Ejemplo n.º 1
0
async def test_resources():
    mailer = SMTPMailer(ssl='contextresource')
    context = Context()
    sslcontext = ssl.create_default_context()
    context.publish_resource(sslcontext, 'contextresource')
    await mailer.start(context)
    assert mailer.ssl is sslcontext
Ejemplo n.º 2
0
async def test_resources():
    mailer = SMTPMailer(tls_context='contextresource')
    context = Context()
    sslcontext = ssl.create_default_context()
    context.add_resource(sslcontext, 'contextresource')
    await mailer.start(context)
    assert mailer.tls_context is sslcontext
Ejemplo n.º 3
0
    async def test_get_resources_include_parents(self, context):
        subcontext = Context(context)
        resource1 = context.publish_resource(6, 'int1')
        resource2 = subcontext.publish_resource(8, 'int2')
        resource3 = context.publish_resource('foo', 'str')

        assert subcontext.get_resources() == [resource1, resource2, resource3]
        assert subcontext.get_resources(include_parents=False) == [resource2]
async def test_multiple_renderers():
    ctx = Context()
    ctx.testvar = 'åäö'
    component = TemplatingComponent({
        'jinja2': {'package_name': 'tests'},
        'mako': {'package_paths': ['tests/templates']}
    })
    await component.start(ctx)

    assert isinstance(ctx.jinja2, TemplateRendererProxy)
    assert isinstance(ctx.mako, TemplateRendererProxy)
Ejemplo n.º 5
0
    async def test_request_resource_parent_add(self, context, event_loop):
        """
        Test that publishing a resource to the parent context will satisfy a resource request in a
        child context.

        """
        child_context = Context(context)
        task = event_loop.create_task(child_context.request_resource(int))
        context.publish_resource(6)
        resource = await task
        assert resource == 6
async def test_single_renderer():
    ctx = Context()
    ctx.testvar = 'åäö'
    component = TemplatingComponent(backend='jinja2', package_name='tests')
    await component.start(ctx)

    assert isinstance(ctx.jinja2, TemplateRendererProxy)
    assert type(ctx.jinja2.environment).__name__ == 'Environment'
    assert ctx.jinja2.render('jinja2_context.html') == """\
<div>
    This is a sample
    Test variable: åäö
</div>"""
    assert ctx.jinja2.render_string('This is testvar: {{ ctx.testvar }}') == 'This is testvar: åäö'
Ejemplo n.º 7
0
    async def start(self, ctx: Context):
        ctx.add_teardown_callback(self.teardown_callback, pass_exception=True)

        if self.method == 'stop':
            ctx.loop.call_later(0.1, ctx.loop.stop)
        elif self.method == 'exit':
            ctx.loop.call_later(0.1, sys.exit)
        elif self.method == 'keyboard':
            ctx.loop.call_later(0.1, self.press_ctrl_c)
        elif self.method == 'sigterm':
            ctx.loop.call_later(0.1, sigterm_handler, logging.getLogger(__name__), ctx.loop)
        elif self.method == 'exception':
            raise RuntimeError('this should crash the application')
        elif self.method == 'timeout':
            await asyncio.sleep(1)
Ejemplo n.º 8
0
    async def start(self, ctx: Context):
        ctx.add_teardown_callback(self.teardown_callback, pass_exception=True)

        if self.method == 'stop':
            ctx.loop.call_later(0.1, ctx.loop.stop)
        elif self.method == 'exit':
            ctx.loop.call_later(0.1, sys.exit)
        elif self.method == 'keyboard':
            ctx.loop.call_later(0.1, self.press_ctrl_c)
        elif self.method == 'sigterm':
            ctx.loop.call_later(0.1, sigterm_handler, logging.getLogger(__name__), ctx.loop)
        elif self.method == 'exception':
            raise RuntimeError('this should crash the application')
        elif self.method == 'timeout':
            await asyncio.sleep(1)
async def test_finish_commit(raise_exception, tmp_path):
    """
    Tests that the session is automatically committed if and only if the context was not
    exited with an exception.

    """
    db_path = tmp_path / "test.db"
    engine = create_engine(f"sqlite:///{db_path}", poolclass=NullPool)
    with engine.begin() as connection:
        connection.execute(text("CREATE TABLE foo (id INTEGER PRIMARY KEY)"))

        component = SQLAlchemyComponent(url={
            "drivername": "sqlite",
            "database": str(db_path)
        }, )
        with ExitStack() as stack:
            async with Context() as ctx:
                await component.start(ctx)
                session = ctx.require_resource(Session)
                session.execute(text("INSERT INTO foo (id) VALUES(3)"))
                if raise_exception:
                    stack.enter_context(pytest.raises(Exception,
                                                      match="dummy"))
                    raise Exception("dummy")

        rows = connection.execute(text("SELECT * FROM foo")).fetchall()
        assert len(rows) == (0 if raise_exception else 1)
async def test_component_multiple(caplog):
    caplog.set_level(logging.INFO, logger='asphalt.mailer.component')
    component = MailerComponent(
        mailers={
            'smtp': {
                'backend': 'smtp',
                'context_attr': 'mailer1'
            },
            'sendmail': {
                'backend': 'sendmail',
                'context_attr': 'mailer2'
            }
        })
    async with Context() as ctx:
        await component.start(ctx)
        assert isinstance(ctx.mailer1, Mailer)
        assert isinstance(ctx.mailer2, Mailer)

    records = [
        record for record in caplog.records
        if record.name == 'asphalt.mailer.component'
    ]
    records.sort(key=lambda r: r.message)
    assert len(records) == 4
    assert records[0].message.startswith(
        "Configured mailer (sendmail / ctx.mailer2; class=SendmailMailer)")
    assert records[1].message.startswith(
        "Configured mailer (smtp / ctx.mailer1; class=SMTPMailer)")
    assert records[2].message.startswith('Mailer (sendmail) stopped')
    assert records[3].message.startswith('Mailer (smtp) stopped')
Ejemplo n.º 11
0
    def test_get_parent_attribute(self, context):
        """
        Test that accessing a nonexistent attribute on a context retrieves the value from parent.

        """
        child_context = Context(context)
        context.a = 2
        assert child_context.a == 2
Ejemplo n.º 12
0
def mailer(event_loop, unused_tcp_port, client_tls_context, smtp_server, tls):
    mailer = SMTPMailer(port=unused_tcp_port,
                        timeout=1,
                        tls=tls,
                        tls_context=client_tls_context)
    with Context() as ctx:
        event_loop.run_until_complete(mailer.start(ctx))
        yield mailer
Ejemplo n.º 13
0
async def test_default_config():
    component = SerializationComponent(backend='json')
    async with Context() as ctx:
        await component.start(ctx)

        resource = ctx.require_resource(Serializer)
        assert isinstance(resource, JSONSerializer)
        assert ctx.json is resource
Ejemplo n.º 14
0
    def test_run_return_5(self, event_loop):
        class DummyCLIComponent(CLIApplicationComponent):
            async def run(self, ctx: Context):
                return 5

        component = DummyCLIComponent()
        event_loop.run_until_complete(component.start(Context()))
        exc = pytest.raises(SystemExit, event_loop.run_forever)
        assert exc.value.code == 5
Ejemplo n.º 15
0
async def test_multiple_engines():
    component = SQLAlchemyComponent(engines={'db1': {}, 'db2': {}}, url='sqlite:///:memory:')
    async with Context() as ctx:
        await component.start(ctx)

        engine1 = ctx.require_resource(Engine, 'db1')
        engine2 = ctx.require_resource(Engine, 'db2')
        assert ctx.db1.bind is engine1
        assert ctx.db2.bind is engine2
Ejemplo n.º 16
0
async def test_bind():
    """Test that a Connection can be passed as "bind" in place of "url"."""
    engine = create_engine('sqlite:///:memory:')
    connection = engine.connect()
    component = SQLAlchemyComponent(bind=connection)
    async with Context() as ctx:
        await component.start(ctx)

        assert ctx.require_resource(Engine) is engine
        assert ctx.sql.bind is connection
async def test_component_start_async():
    """Test that the component creates all the expected (asynchronous) resources."""
    url = URL.create("sqlite+aiosqlite", database=":memory:")
    component = SQLAlchemyComponent(url=url)
    async with Context() as ctx:
        await component.start(ctx)

        ctx.require_resource(AsyncEngine)
        ctx.require_resource(sessionmaker)
        ctx.require_resource(AsyncSession)
Ejemplo n.º 18
0
async def test_component_start(poolclass):
    """Test that the component creates all the expected resources."""
    component = SQLAlchemyComponent(url='sqlite:///:memory:', poolclass=poolclass)
    async with Context() as ctx:
        await component.start(ctx)

        engine = ctx.require_resource(Engine)
        ctx.require_resource(sessionmaker)
        assert ctx.sql is ctx.require_resource(Session)
        assert ctx.sql.bind is engine
Ejemplo n.º 19
0
async def test_memory_leak():
    """Test that creating a session in a context does not leak memory."""
    component = SQLAlchemyComponent(url='sqlite:///:memory:')
    async with Context() as ctx:
        await component.start(ctx)
        assert isinstance(ctx.sql, Session)

    del ctx
    gc.collect()  # needed on PyPy
    assert next((x for x in gc.get_objects() if isinstance(x, Context)), None) is None
async def test_finish_commit(raise_exception, executor, commit_executor, tmpdir):
    """
    Tests that the session is automatically committed if and only if the context was not exited
    with an exception.

    """
    db_path = tmpdir.join('test.db')
    session = dict(commit_executor=executor if commit_executor == 'instance' else commit_executor)
    component = SQLAlchemyComponent(url={'drivername': 'sqlite', 'database': str(db_path)},
                                    session=session)
    ctx = Context()
    ctx.publish_resource(executor, types=[Executor])
    await component.start(ctx)
    ctx.dbsession.execute('CREATE TABLE foo (id INTEGER PRIMARY KEY)')
    ctx.dbsession.execute('INSERT INTO foo (id) VALUES(3)')
    await ctx.finished.dispatch(Exception('dummy') if raise_exception else None,
                                return_future=True)

    rows = ctx.sql.execute('SELECT * FROM foo').fetchall()
    assert len(rows) == (0 if raise_exception else 1)
async def test_bind_async():
    """Test that a Connection can be passed as "bind" in place of "url"."""
    engine = create_async_engine("sqlite+aiosqlite:///:memory:")
    connection = await engine.connect()
    component = SQLAlchemyComponent(bind=connection)
    async with Context() as ctx:
        await component.start(ctx)

        assert ctx.require_resource(AsyncEngine) is engine
        assert ctx.require_resource(AsyncSession).bind is connection

    await connection.close()
Ejemplo n.º 22
0
async def test_finish_commit(raise_exception, executor, commit_executor, tmpdir):
    """
    Tests that the session is automatically committed if and only if the context was not exited
    with an exception.

    """
    db_path = tmpdir.join('test.db')
    engine = create_engine('sqlite:///%s' % db_path, poolclass=NullPool)
    engine.execute('CREATE TABLE foo (id INTEGER PRIMARY KEY)')

    component = SQLAlchemyComponent(
        url={'drivername': 'sqlite', 'database': str(db_path)},
        commit_executor=executor if commit_executor == 'instance' else commit_executor)
    ctx = Context()
    ctx.add_resource(executor, types=[Executor])
    await component.start(ctx)
    ctx.sql.execute('INSERT INTO foo (id) VALUES(3)')
    await ctx.close(Exception('dummy') if raise_exception else None)

    rows = engine.execute('SELECT * FROM foo').fetchall()
    assert len(rows) == (0 if raise_exception else 1)
Ejemplo n.º 23
0
    def test_run_return_invalid_type(self, event_loop):
        class DummyCLIComponent(CLIApplicationComponent):
            async def run(self, ctx: Context):
                return 'foo'

        component = DummyCLIComponent()
        event_loop.run_until_complete(component.start(Context()))
        with pytest.warns(UserWarning) as record:
            exc = pytest.raises(SystemExit, event_loop.run_forever)

        assert exc.value.code == 1
        assert len(record) == 1
        assert str(record[0].message) == 'run() must return an integer or None, not str'
Ejemplo n.º 24
0
    def test_run_return_invalid_value(self, event_loop):
        class DummyCLIComponent(CLIApplicationComponent):
            async def run(self, ctx: Context):
                return 128

        component = DummyCLIComponent()
        event_loop.run_until_complete(component.start(Context()))
        with pytest.warns(UserWarning) as record:
            exc = pytest.raises(SystemExit, event_loop.run_forever)

        assert exc.value.code == 1
        assert len(record) == 1
        assert str(record[0].message) == 'exit code out of range: 128'
Ejemplo n.º 25
0
def test_event_context():
    parent = Context()
    session_details = SessionDetails('default', 5)
    event_details = EventDetails(publication=15, publisher=8, publisher_authid='user',
                                 publisher_authrole='role', topic='topic')
    context = EventContext(parent, session_details, event_details)

    assert context.session_id == 5
    assert context.publisher_session_id == 8
    assert context.publisher_auth_id == 'user'
    assert context.publisher_auth_role == 'role'
    assert context.publication_id == 15
    assert context.topic == 'topic'
    assert context.enc_algo is None
Ejemplo n.º 26
0
async def test_null_configs():
    component = SerializationComponent(serializers={
        'json': None,
        'msgpack': None,
        'pickle': None,
        'yaml': None
    })
    async with Context() as ctx:
        await component.start(ctx)

        assert ctx.json
        assert ctx.msgpack
        assert ctx.pickle
        assert ctx.yaml
Ejemplo n.º 27
0
async def test_component_start():
    component = SerializationComponent(serializers={
        'json': {'encoder_options': {'allow_nan': False}},
        'msgpack': {'unpacker_options': {'encoding': 'iso-8859-1'}},
        'pickle': {'protocol': 3},
        'yaml': {'safe': False}
    })
    async with Context() as ctx:
        await component.start(ctx)

        assert ctx.json
        assert ctx.msgpack
        assert ctx.pickle
        assert ctx.yaml
async def test_close_twice_async(asyncpg_url):
    """Test that closing a session releases connection resources, but remains usable."""
    component = SQLAlchemyComponent(url=asyncpg_url)
    async with Context() as ctx:
        await component.start(ctx)
        session = ctx.require_resource(AsyncSession)
        pool = session.bind.pool
        await session.execute(text("SELECT 1"))
        assert pool.checkedout() == 1
        await session.close()
        assert pool.checkedout() == 0
        await session.execute(text("SELECT 1"))
        assert pool.checkedout() == 1

    assert pool.checkedout() == 0
async def test_component_single(caplog, backend):
    caplog.set_level(logging.INFO, logger='asphalt.mailer.component')
    component = MailerComponent(backend=backend)
    async with Context() as ctx:
        await component.start(ctx)
        assert isinstance(ctx.mailer, Mailer)

    records = [
        record for record in caplog.records
        if record.name == 'asphalt.mailer.component'
    ]
    records.sort(key=lambda r: r.message)
    assert len(records) == 2
    assert records[0].message.startswith(
        "Configured mailer (default / ctx.mailer; class=%s)" %
        ctx.mailer.__class__.__name__)
    assert records[1].message.startswith('Mailer (default) stopped')
Ejemplo n.º 30
0
    async def test_context_teardown(self, expected_exc):
        @context_teardown
        async def start(ctx: Context):
            nonlocal phase, received_exception
            phase = 'started'
            exc = yield
            phase = 'finished'
            received_exception = exc

        phase = received_exception = None
        context = Context()
        await start(context)
        assert phase == 'started'

        await context.close(expected_exc)
        assert phase == 'finished'
        assert received_exception == expected_exc
Ejemplo n.º 31
0
def test_call_context():
    def progress(arg):
        pass

    parent = Context()
    session_details = SessionDetails('default', 5)
    call_details = CallDetails(progress=progress, caller=8, caller_authid='user',
                               caller_authrole='role', procedure='procedurename')
    context = CallContext(parent, session_details, call_details)

    assert context.session_id == 5
    assert context.caller_session_id == 8
    assert context.caller_auth_id == 'user'
    assert context.caller_auth_role == 'role'
    assert context.procedure == 'procedurename'
    assert context.enc_algo is None
    assert context.progress is progress
Ejemplo n.º 32
0
async def test_single_client(event_loop):
    ctx = Context()
    component = WAMPComponent(ssl='default', serializer='default')
    ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    serializer = JSONSerializer()
    ctx.publish_resource(serializer, types=[Serializer])
    ctx.publish_resource(ssl_context)
    await component.start(ctx)

    assert isinstance(ctx.wamp, WAMPClient)
    assert ctx.wamp.ssl is ssl_context
    assert ctx.wamp.serializer is serializer
Ejemplo n.º 33
0
async def test_single_renderer():
    async with Context() as ctx:
        ctx.add_resource("åäö")
        component = TemplatingComponent(backend="jinja2",
                                        options={"package_name": "tests"})
        await component.start(ctx)

        renderer = ctx.require_resource(TemplateRenderer)
        assert isinstance(renderer, TemplateRendererProxy)

        assert type(renderer.environment).__name__ == "Environment"
        assert (renderer.render("jinja2_context.html", str=str) == """\
<div>
    This is a sample
    Test variable: åäö
</div>""")
        assert (renderer.render_string(
            "This is testvar: {{ ctx.require_resource(str) }}",
            str=str) == "This is testvar: åäö")
Ejemplo n.º 34
0
async def test_ready_callback(asynchronous):
    def ready_callback(engine, factory):
        nonlocal engine2, factory2
        engine2 = engine
        factory2 = factory

    async def ready_callback_async(engine, factory):
        nonlocal engine2, factory2
        engine2 = engine
        factory2 = factory

    engine2 = factory2 = None
    callback = ready_callback_async if asynchronous else ready_callback
    component = SQLAlchemyComponent(url='sqlite:///:memory:', ready_callback=callback)
    async with Context() as ctx:
        await component.start(ctx)

        engine = ctx.require_resource(Engine)
        factory = ctx.require_resource(sessionmaker)
        assert engine is engine2
        assert factory is factory2
async def test_session_event_async(request, asyncpg_url, psycopg2_url):
    """Test that creating a session in a context does not leak memory."""
    listener_session: Session
    listener_thread: Thread

    def listener(session: Session) -> None:
        nonlocal listener_session, listener_thread
        try:
            async_session = get_resource(AsyncSession)
        except NoCurrentContext:
            return

        if async_session and session is async_session.sync_session:
            listener_session = session
            listener_thread = current_thread()

    listen(Session, "before_commit", listener)
    request.addfinalizer(lambda: remove(Session, "before_commit", listener))
    component = SQLAlchemyComponent(url=asyncpg_url)
    async with Context() as ctx:
        await component.start(ctx)
        dbsession = ctx.require_resource(AsyncSession)
        await dbsession.run_sync(
            lambda session: Person.metadata.create_all(session.bind))
        dbsession.add(Person(name="Test person"))

    assert listener_session is dbsession.sync_session
    assert listener_thread is current_thread()

    engine = create_engine(psycopg2_url)
    with Session(engine) as sess:
        sess.add(Person(name="Test person 2"))
        sess.commit()

    engine.dispose()

    assert listener_session is dbsession.sync_session
    assert listener_thread is current_thread()
Ejemplo n.º 36
0
 async def start(self, ctx: Context):
     for resource_name, context_attr, mailer in self.mailers:
         await mailer.start(ctx)
         ctx.publish_resource(mailer, resource_name, context_attr, types=[Mailer])
         logger.info('Configured mailer (%s / ctx.%s)', resource_name, context_attr)
Ejemplo n.º 37
0
def run_application(component: Union[Component, Dict[str, Any]], *, event_loop_policy: str = None,
                    max_threads: int = None, logging: Union[Dict[str, Any], int, None] = INFO,
                    start_timeout: Union[int, float, None] = 10):
    """
    Configure logging and start the given root component in the default asyncio event loop.

    Assuming the root component was started successfully, the event loop will continue running
    until the process is terminated.

    Initializes the logging system first based on the value of ``logging``:
      * If the value is a dictionary, it is passed to :func:`logging.config.dictConfig` as
        argument.
      * If the value is an integer, it is passed to :func:`logging.basicConfig` as the logging
        level.
      * If the value is ``None``, logging setup is skipped entirely.

    By default, the logging system is initialized using :func:`~logging.basicConfig` using the
    ``INFO`` logging level.

    The default executor in the event loop is replaced with a new
    :class:`~concurrent.futures.ThreadPoolExecutor` where the maximum number of threads is set to
    the value of ``max_threads`` or, if omitted, the default value of
    :class:`~concurrent.futures.ThreadPoolExecutor`.

    :param component: the root component (either a component instance or a configuration dictionary
        where the special ``type`` key is either a component class or a ``module:varname``
        reference to one)
    :param event_loop_policy: entry point name (from the ``asphalt.core.event_loop_policies``
        namespace) of an alternate event loop policy (or a module:varname reference to one)
    :param max_threads: the maximum number of worker threads in the default thread pool executor
        (the default value depends on the event loop implementation)
    :param logging: a logging configuration dictionary, :ref:`logging level <python:levels>` or
        ``None``
    :param start_timeout: seconds to wait for the root component (and its subcomponents) to start
        up before giving up (``None`` = wait forever)

    """
    assert check_argument_types()

    # Configure the logging system
    if isinstance(logging, dict):
        dictConfig(logging)
    elif isinstance(logging, int):
        basicConfig(level=logging)

    # Inform the user whether -O or PYTHONOPTIMIZE was set when Python was launched
    logger = getLogger(__name__)
    logger.info('Running in %s mode', 'development' if __debug__ else 'production')

    # Switch to an alternate event loop policy if one was provided
    if event_loop_policy:
        create_policy = policies.resolve(event_loop_policy)
        policy = create_policy()
        asyncio.set_event_loop_policy(policy)
        logger.info('Switched event loop policy to %s', qualified_name(policy))

    # Assign a new default executor with the given max worker thread limit if one was provided
    event_loop = asyncio.get_event_loop()
    if max_threads is not None:
        event_loop.set_default_executor(ThreadPoolExecutor(max_threads))
        logger.info('Installed a new thread pool executor with max_workers=%d', max_threads)

    # Instantiate the root component if a dict was given
    if isinstance(component, dict):
        component = cast(Component, component_types.create_object(**component))

    logger.info('Starting application')
    context = Context()
    exception = None  # type: Optional[BaseException]
    exit_code = 0

    # Start the root component
    try:
        coro = asyncio.wait_for(component.start(context), start_timeout, loop=event_loop)
        event_loop.run_until_complete(coro)
    except asyncio.TimeoutError as e:
        exception = e
        logger.error('Timeout waiting for the root component to start')
        exit_code = 1
    except Exception as e:
        exception = e
        logger.exception('Error during application startup')
        exit_code = 1
    else:
        logger.info('Application started')

        # Add a signal handler to gracefully deal with SIGTERM
        try:
            event_loop.add_signal_handler(signal.SIGTERM, sigterm_handler, logger, event_loop)
        except NotImplementedError:
            pass  # Windows does not support signals very well

        # Finally, run the event loop until the process is terminated or Ctrl+C is pressed
        try:
            event_loop.run_forever()
        except KeyboardInterrupt:
            pass
        except SystemExit as e:
            exit_code = e.code

    # Close the root context
    logger.info('Stopping application')
    event_loop.run_until_complete(context.close(exception))

    # Shut down leftover async generators (requires Python 3.6+)
    try:
        event_loop.run_until_complete(event_loop.shutdown_asyncgens())
    except (AttributeError, NotImplementedError):
        pass

    # Finally, close the event loop itself
    event_loop.close()
    logger.info('Application stopped')

    # Shut down the logging system
    shutdown()

    if exit_code:
        sys.exit(exit_code)