From 3d8afdd8ebbcd9c17d2eb9b7cf4c2913d9f02f20 Mon Sep 17 00:00:00 2001 From: David Erb Date: Wed, 17 May 2023 07:27:43 +0100 Subject: [PATCH] move client instance out of service context --- src/dls_servbase_lib/datafaces/context.py | 66 +++++++---- tests/test_dataface.py | 138 +++++++++++----------- tests/test_dataface_takeover.py | 58 +++++---- 3 files changed, 143 insertions(+), 119 deletions(-) diff --git a/src/dls_servbase_lib/datafaces/context.py b/src/dls_servbase_lib/datafaces/context.py index 450da92..e606353 100644 --- a/src/dls_servbase_lib/datafaces/context.py +++ b/src/dls_servbase_lib/datafaces/context.py @@ -1,6 +1,5 @@ import logging - -from dls_servbase_api.datafaces.context import Context as DatafaceContext +from typing import Dict # Base class for an asyncio context from dls_servbase_lib.contexts.base import Base as ContextBase @@ -16,23 +15,34 @@ class Context(ContextBase): """ - Asyncio context for a dls_servbase_dataface server object. + Asyncio context for a servbase object. On entering, it creates the object according to the specification (a dict). - If configured, it starts the server as a coroutine, thread or process. - On exiting, it commands the server to shut down. - - The enter and exit methods are exposed for use during testing. + If specified, it starts the server as a coroutine, thread or process. + If not a server, then it will instatiate a direct access to a servbase. + On exiting, it commands the server to shut down and/or releases the direct access resources. """ # ---------------------------------------------------------------------------------------- - def __init__(self, specification): + def __init__(self, specification: Dict): + """ + Constructor. + + Args: + specification (Dict): specification of the servbase object to be constructed within the context. + The only key in the specification that relates to the context is "start_as", which can be "coro", "thread", "process" or None. + All other keys in the specification relate to creating the servbase object. + """ ContextBase.__init__(self, thing_type, specification) - self.__api_context = None - # ---------------------------------------------------------------------------------------- - async def aenter(self): - """ """ + async def aenter(self) -> None: + """ + Asyncio context entry. + + Starts and activates service as specified. + + Establishes the global (singleton-like) default servbase. + """ # Build the object according to the specification. self.server = Datafaces().build_object(self.specification()) @@ -46,16 +56,30 @@ async def aenter(self): elif self.context_specification.get("start_as") == "process": await self.server.start_process() - self.__api_context = DatafaceContext(self.specification()) - await self.__api_context.aenter() + # Not running as a service? + elif self.context_specification.get("start_as") == "direct": + # We need to activate the tick() task. + await self.server.activate() # ---------------------------------------------------------------------------------------- - async def aexit(self): - """ """ + async def aexit(self) -> None: + """ + Asyncio context exit. - if self.server is not None: - # Put in request to shutdown the server. - await self.server.client_shutdown() + Stop service if one was started and releases any client resources. + """ - if self.__api_context is not None: - await self.__api_context.aexit() + if self.server is not None: + if self.context_specification.get("start_as") == "process": + logger.info( + "[DISSHU] in context exit, sending shutdown to client process" + ) + # Put in request to shutdown the server. + await self.server.client_shutdown() + logger.info("[DISSHU] in context exit, sent shutdown to client process") + + if self.context_specification.get("start_as") == "coro": + await self.server.direct_shutdown() + + if self.context_specification.get("start_as") == "direct": + await self.server.deactivate() diff --git a/tests/test_dataface.py b/tests/test_dataface.py index 617ab32..3319e01 100644 --- a/tests/test_dataface.py +++ b/tests/test_dataface.py @@ -1,12 +1,9 @@ import logging from dls_servbase_api.databases.constants import CookieFieldnames, Tablenames - -# Object managing datafaces. +from dls_servbase_api.datafaces.context import Context as ClientContext from dls_servbase_api.datafaces.datafaces import dls_servbase_datafaces_get_default - -# Context creator. -from dls_servbase_lib.contexts.contexts import Contexts +from dls_servbase_lib.datafaces.context import Context as ServerContext # Base class for the tester. from tests.base_context_tester import BaseContextTester @@ -40,65 +37,72 @@ async def _main_coroutine(self, constants, output_directory): context_configuration = await dls_servbase_multiconf.load() - dls_servbase_context = Contexts().build_object(context_configuration) - - async with dls_servbase_context: - dataface = dls_servbase_datafaces_get_default() - - # Write one record. - await dataface.insert( - Tablenames.COOKIES, - [ - { - CookieFieldnames.UUID: "f0", - CookieFieldnames.CONTENTS: "{'a': 'f000'}", - } - ], - ) - - all_sql = f"SELECT * FROM {Tablenames.COOKIES}" - records = await dataface.query(all_sql) - - assert len(records) == 1 - assert records[0][CookieFieldnames.UUID] == "f0" - assert records[0][CookieFieldnames.CONTENTS] == "{'a': 'f000'}" - - # ---------------------------------------------------------------- - # Now try a direct update. - record = { - CookieFieldnames.CONTENTS: "{'b': 'f1111'}", - } - - subs = ["f0"] - result = await dataface.update( - Tablenames.COOKIES, - record, - f"{CookieFieldnames.UUID} = ?", - subs=subs, - why="test update", - ) - - assert result["count"] == 1 - records = await dataface.query(all_sql) - - assert len(records) == 1 - assert records[0][CookieFieldnames.UUID] == "f0" - assert records[0][CookieFieldnames.CONTENTS] == "{'b': 'f1111'}" - - # ---------------------------------------------------------------- - # Now try a high level API update. - record = { - CookieFieldnames.UUID: "f0", - CookieFieldnames.CONTENTS: "{'c': 'f2222'}", - } - - result = await dataface.update_cookie( - record, - ) - - assert result["count"] == 1 - records = await dataface.query(all_sql) - - assert len(records) == 1 - assert records[0][CookieFieldnames.UUID] == "f0" - assert records[0][CookieFieldnames.CONTENTS] == "{'c': 'f2222'}" + servbase_specification = context_configuration[ + "dls_servbase_dataface_specification" + ] + + dls_servbase_client_context = ClientContext(servbase_specification) + + dls_servbase_server_context = ServerContext(servbase_specification) + + async with dls_servbase_client_context: + async with dls_servbase_server_context: + dataface = dls_servbase_datafaces_get_default() + + # Write one record. + await dataface.insert( + Tablenames.COOKIES, + [ + { + CookieFieldnames.UUID: "f0", + CookieFieldnames.CONTENTS: "{'a': 'f000'}", + } + ], + ) + + all_sql = f"SELECT * FROM {Tablenames.COOKIES}" + records = await dataface.query(all_sql) + + assert len(records) == 1 + assert records[0][CookieFieldnames.UUID] == "f0" + assert records[0][CookieFieldnames.CONTENTS] == "{'a': 'f000'}" + + # ---------------------------------------------------------------- + # Now try a direct update. + record = { + CookieFieldnames.CONTENTS: "{'b': 'f1111'}", + } + + subs = ["f0"] + result = await dataface.update( + Tablenames.COOKIES, + record, + f"{CookieFieldnames.UUID} = ?", + subs=subs, + why="test update", + ) + + assert result["count"] == 1 + records = await dataface.query(all_sql) + + assert len(records) == 1 + assert records[0][CookieFieldnames.UUID] == "f0" + assert records[0][CookieFieldnames.CONTENTS] == "{'b': 'f1111'}" + + # ---------------------------------------------------------------- + # Now try a high level API update. + record = { + CookieFieldnames.UUID: "f0", + CookieFieldnames.CONTENTS: "{'c': 'f2222'}", + } + + result = await dataface.update_cookie( + record, + ) + + assert result["count"] == 1 + records = await dataface.query(all_sql) + + assert len(records) == 1 + assert records[0][CookieFieldnames.UUID] == "f0" + assert records[0][CookieFieldnames.CONTENTS] == "{'c': 'f2222'}" diff --git a/tests/test_dataface_takeover.py b/tests/test_dataface_takeover.py index b6411a9..eff4511 100644 --- a/tests/test_dataface_takeover.py +++ b/tests/test_dataface_takeover.py @@ -1,13 +1,9 @@ import logging from dls_servbase_api.databases.constants import CookieFieldnames, Tablenames - -# Object managing datafaces. +from dls_servbase_api.datafaces.context import Context as ClientContext from dls_servbase_api.datafaces.datafaces import dls_servbase_datafaces_get_default - -# Context creator. -from dls_servbase_lib.contexts.contexts import Contexts -from dls_servbase_lib.datafaces.context import Context as DatafaceContext +from dls_servbase_lib.datafaces.context import Context as ServerContext # Base class for the tester. from tests.base_context_tester import BaseContextTester @@ -36,37 +32,37 @@ async def _main_coroutine(self, constants, output_directory): dls_servbase_multiconf = self.get_multiconf() context_configuration = await dls_servbase_multiconf.load() - dls_servbase_context = Contexts().build_object(context_configuration) - - async with dls_servbase_context: - dataface = dls_servbase_datafaces_get_default() - - # Write one record. - await dataface.insert( - Tablenames.COOKIES, - [ - { - CookieFieldnames.UUID: "f0", - CookieFieldnames.CONTENTS: "{'a': 'f000'}", - } - ], - ) - # Make a new dataface context with the same specification. - dataface_specification = dls_servbase_multiconf.require( - "dls_servbase_dataface_specification" - ) + servbase_specification = context_configuration[ + "dls_servbase_dataface_specification" + ] + + dls_servbase_client_context = ClientContext(servbase_specification) + + dls_servbase_server_context = ServerContext(servbase_specification) + async with dls_servbase_client_context: + async with dls_servbase_server_context: + dataface = dls_servbase_datafaces_get_default() + + # Write one record. + await dataface.insert( + Tablenames.COOKIES, + [ + { + CookieFieldnames.UUID: "f0", + CookieFieldnames.CONTENTS: "{'a': 'f000'}", + } + ], + ) + + # Make a new dataface context with the same specification. + dls_servbase_server_context = ServerContext(servbase_specification) # Activate the new dataface which should send shutdown to the old process. - async with DatafaceContext(dataface_specification): + async with dls_servbase_server_context: all_sql = f"SELECT * FROM {Tablenames.COOKIES}" records = await dataface.query(all_sql) assert len(records) == 1 assert records[0][CookieFieldnames.UUID] == "f0" assert records[0][CookieFieldnames.CONTENTS] == "{'a': 'f000'}" - - # From the top level context's point of view, one of its processs is dead. - assert await dls_servbase_context.is_any_process_dead() - - assert not await dls_servbase_context.is_any_process_alive()