From 5bc8d30d5fa04a3aa187403d8e5472091f4b4c5e Mon Sep 17 00:00:00 2001 From: Duc Trung LE Date: Tue, 16 Apr 2024 19:14:22 +0200 Subject: [PATCH] Add docstring --- .github/workflows/build.yml | 6 +++-- pyproject.toml | 5 +++- tljh_repo2docker/binderhub_builder.py | 14 ++++++++++ tljh_repo2docker/binderhub_log.py | 18 +++++++++++++ tljh_repo2docker/dbutil.py | 27 +++++++++++-------- .../tests/binderhub_build/test_builder.py | 3 ++- .../tests/binderhub_build/test_images.py | 4 +-- 7 files changed, 60 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ab75e8..400b86d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,12 +92,14 @@ jobs: npm -g install configurable-http-proxy - name: Run local build backend tests + working-directory: tljh_repo2docker/tests run: | - python -m pytest tljh_repo2docker/tests/local_build --cov + python -m pytest local_build --cov - name: Run binderhub build backend tests + working-directory: tljh_repo2docker/tests run: | - python -m pytest tljh_repo2docker/tests/binderhub_build --cov + python -m pytest binderhub_build --cov integration-tests: name: Integration tests diff --git a/pyproject.toml b/pyproject.toml index 928b3c8..07e1d86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,10 +14,13 @@ dependencies = [ "jupyter-repo2docker>=2024,<2025", ] dynamic = ["version"] -license = { file = "LICENSE" } +license = {file = "LICENSE"} name = "tljh-repo2docker" readme = "README.md" +[project.scripts] +tljh_repo2docker_upgrade_db = "tljh_repo2docker.dbutil:main" + [project.entry-points.tljh] tljh_repo2docker = "tljh_repo2docker" diff --git a/tljh_repo2docker/binderhub_builder.py b/tljh_repo2docker/binderhub_builder.py index 2ddedec..9a854f1 100644 --- a/tljh_repo2docker/binderhub_builder.py +++ b/tljh_repo2docker/binderhub_builder.py @@ -26,6 +26,13 @@ class BinderHubBuildHandler(BaseHandler): @web.authenticated @require_admin_role async def delete(self): + """ + Method to handle the deletion of a specific image. + + Note: + - Only users with admin role or with `TLJH_R2D_ADMIN_SCOPE` scope can access it. + """ + data = self.get_json_body() uid = UUID(data["name"]) @@ -55,6 +62,13 @@ async def delete(self): @web.authenticated @require_admin_role async def post(self): + """ + Method to handle the creation of a new environment based on provided specifications. + As the build progresses, it updates the build log in the database and checks for completion. + + Note: + - Only users with admin role or with `TLJH_R2D_ADMIN_SCOPE` scope can access it. + """ data = self.get_json_body() repo = data["repo"] diff --git a/tljh_repo2docker/binderhub_log.py b/tljh_repo2docker/binderhub_log.py index 71bf541..faf90ce 100644 --- a/tljh_repo2docker/binderhub_log.py +++ b/tljh_repo2docker/binderhub_log.py @@ -17,6 +17,20 @@ class BinderHubLogsHandler(BaseHandler): @web.authenticated @require_admin_role async def get(self, image_uid: str): + """ + Method to retrieve real-time status updates for a specific image build process. + + This method sets the appropriate headers for Server-Sent Events (SSE) to enable streaming of data over HTTP. + It retrieves the database handlers and the image with the specified UID from the database. + Then, it continuously emits status updates over the stream until the build process is completed or times out. + + Parameters: + - image_uid (str): The UID of the image for which real-time status updates are requested. + + Raises: + - web.HTTPError: If the provided image UID is badly formed or if the requested image is not found. + """ + self.set_header("Content-Type", "text/event-stream") self.set_header("Cache-Control", "no-cache") @@ -64,6 +78,10 @@ async def get(self, image_uid: str): break async def _emit(self, msg): + """ + Asynchronous method to emit a message over a stream. + """ + try: self.write(f"data: {json.dumps(msg)}\n\n") await self.flush() diff --git a/tljh_repo2docker/dbutil.py b/tljh_repo2docker/dbutil.py index 8bd06f4..b87e20b 100644 --- a/tljh_repo2docker/dbutil.py +++ b/tljh_repo2docker/dbutil.py @@ -130,7 +130,7 @@ def check_db_revision(engine): else: raise Exception( f"Found database schema version {alembic_revision} != {head}. " - "Backup your database and run `tljh_repo2docker upgrade-db`" + "Backup your database and run `tljh_repo2docker_upgrade_db`" " to upgrade to the latest schema." ) @@ -187,6 +187,16 @@ def async_to_sync_url(db_url: str) -> str: def async_session_context_factory(async_db_url: str): + """ + Factory function to create an asynchronous session context manager. + + Parameters: + - async_db_url (str): The URL for the asynchronous database connection. + + Returns: + - AsyncContextManager[AsyncSession]: An asynchronous context manager that yields + an async session for database interactions within the context. + """ async_engine = create_async_engine(async_db_url) async_session_maker = async_sessionmaker( async_engine, @@ -204,14 +214,9 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]: return async_session_context -def main(args=None): - if args is None: +def main(): + if len(sys.argv) > 1: db_url = sys.argv[1] - alembic_args = sys.argv[2:] - # dumb option parsing, since we want to pass things through - # to subcommands - _alembic(db_url, alembic_args) - - -if __name__ == "__main__": - sys.exit(main()) + upgrade(db_url) + else: + print("Missing database URL") diff --git a/tljh_repo2docker/tests/binderhub_build/test_builder.py b/tljh_repo2docker/tests/binderhub_build/test_builder.py index 0b2fcdb..e87ed9a 100644 --- a/tljh_repo2docker/tests/binderhub_build/test_builder.py +++ b/tljh_repo2docker/tests/binderhub_build/test_builder.py @@ -1,7 +1,7 @@ import pytest import sqlalchemy as sa from aiodocker import Docker, DockerError - +import asyncio from tljh_repo2docker.database.model import DockerImageSQL from ..utils import add_environment, remove_environment, wait_for_image @@ -20,6 +20,7 @@ async def test_add_environment( assert uid is not None await wait_for_image(image_name=generated_image_name) + await asyncio.sleep(3) images_db = db_session.execute(sa.select(DockerImageSQL)).scalars().first() assert images_db.name == generated_image_name assert images_db.image_meta["display_name"] == name diff --git a/tljh_repo2docker/tests/binderhub_build/test_images.py b/tljh_repo2docker/tests/binderhub_build/test_images.py index ceef9b2..3ba408d 100644 --- a/tljh_repo2docker/tests/binderhub_build/test_images.py +++ b/tljh_repo2docker/tests/binderhub_build/test_images.py @@ -1,6 +1,6 @@ import pytest from jupyterhub.tests.utils import get_page - +import asyncio from ..utils import add_environment, get_service_page, wait_for_image @@ -45,7 +45,7 @@ async def test_spawn_page(app, minimal_repo, image_name, generated_image_name): ) assert r.status_code == 200 await wait_for_image(image_name=generated_image_name) - + await asyncio.sleep(3) # the environment should be on the page r = await get_page( "services/tljh_repo2docker/environments",