Skip to content

Commit

Permalink
Switch to Asphalt v5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrochart committed Apr 16, 2024
1 parent 77e2e94 commit 0268970
Show file tree
Hide file tree
Showing 60 changed files with 1,072 additions and 1,035 deletions.
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,3 @@ $RECYCLE.BIN/
.jupyter_ystore.db
.jupyter_ystore.db-journal
fps_cli_args.toml

# pixi environments
.pixi
4 changes: 1 addition & 3 deletions jupyverse_api/jupyverse_api/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import List, Tuple

import rich_click as click
from asphalt.core.cli import run
from asphalt.core._cli import run

if sys.version_info < (3, 10):
from importlib_metadata import entry_points
Expand Down Expand Up @@ -66,8 +66,6 @@ def main(
set_list.append(f"component.allow_origin={allow_origin}")
config = get_config(disable)
run.callback(
unsafe=False,
loop=None,
set_=set_list,
service=None,
configfile=[config],
Expand Down
10 changes: 7 additions & 3 deletions jupyverse_api/jupyverse_api/contents/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import asyncio
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Dict, List, Optional, Union
Expand All @@ -13,8 +12,13 @@


class FileIdManager(ABC):
stop_watching_files: asyncio.Event
stopped_watching_files: asyncio.Event
@abstractmethod
async def start(self) -> None:
...

@abstractmethod
async def stop(self) -> None:
...

@abstractmethod
async def get_path(self, file_id: str) -> str:
Expand Down
36 changes: 22 additions & 14 deletions jupyverse_api/jupyverse_api/main/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import webbrowser
from typing import Any, Callable, Dict, Sequence, Tuple

from anyio import Event
from asgiref.typing import ASGI3Application
from asphalt.core import Component, Context
from asphalt.core import Component, add_resource, get_resource, start_service_task
from asphalt.web.fastapi import FastAPIComponent
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
Expand All @@ -22,14 +23,11 @@ def __init__(
super().__init__()
self.mount_path = mount_path

async def start(
self,
ctx: Context,
) -> None:
app = await ctx.request_resource(FastAPI)
async def start(self) -> None:
app = await get_resource(FastAPI, wait=True)

_app = App(app, mount_path=self.mount_path)
ctx.add_resource(_app)
add_resource(_app)


class JupyverseComponent(FastAPIComponent):
Expand Down Expand Up @@ -67,22 +65,27 @@ def __init__(
self.port = port
self.open_browser = open_browser
self.query_params = query_params
self.lifespan = Lifespan()

async def start(
self,
ctx: Context,
) -> None:
async def start(self) -> None:
query_params = QueryParams(d={})
host = self.host
if not host.startswith("http"):
host = f"http://{host}"
host_url = Host(url=f"{host}:{self.port}/")
ctx.add_resource(query_params)
ctx.add_resource(host_url)
add_resource(query_params)
add_resource(host_url)
add_resource(self.lifespan)

await super().start(ctx)
await super().start()

# at this point, the server has started
await start_service_task(
self.lifespan.shutdown_request.wait,
"Server lifespan notifier",
teardown_action=self.lifespan.shutdown_request.set,
)

if self.open_browser:
qp = query_params.d
if self.query_params:
Expand All @@ -97,3 +100,8 @@ class QueryParams(BaseModel):

class Host(BaseModel):
url: str


class Lifespan:
def __init__(self):
self.shutdown_request = Event()
6 changes: 4 additions & 2 deletions jupyverse_api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ classifiers = [
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
Expand All @@ -28,8 +29,9 @@ dependencies = [
"pydantic >=2,<3",
"fastapi >=0.95.0,<1",
"rich-click >=1.6.1,<2",
"asphalt >=4.11.0,<5",
"asphalt-web[fastapi] >=1.1.0,<2",
"importlib_metadata >=3.6; python_version<'3.10'",
#"asphalt >=4.11.0,<5",
#"asphalt-web[fastapi] >=1.1.0,<2",
]
dynamic = ["version"]

Expand Down
19 changes: 8 additions & 11 deletions plugins/auth/fps_auth/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging

from asphalt.core import Component, Context
from asphalt.core import Component, add_resource, get_resource
from fastapi_users.exceptions import UserAlreadyExists

from jupyverse_api.app import App
Expand All @@ -18,17 +18,14 @@ class AuthComponent(Component):
def __init__(self, **kwargs):
self.auth_config = _AuthConfig(**kwargs)

async def start(
self,
ctx: Context,
) -> None:
ctx.add_resource(self.auth_config, types=AuthConfig)
async def start(self) -> None:
add_resource(self.auth_config, types=AuthConfig)

app = await ctx.request_resource(App)
frontend_config = await ctx.request_resource(FrontendConfig)
app = await get_resource(App, wait=True)
frontend_config = await get_resource(FrontendConfig, wait=True)

auth = auth_factory(app, self.auth_config, frontend_config)
ctx.add_resource(auth, types=Auth)
add_resource(auth, types=Auth)

await auth.db.create_db_and_tables()

Expand Down Expand Up @@ -59,8 +56,8 @@ async def start(
)

if self.auth_config.mode == "token":
query_params = await ctx.request_resource(QueryParams)
host = await ctx.request_resource(Host)
query_params = await get_resource(QueryParams, wait=True)
host = await get_resource(Host, wait=True)
query_params.d["token"] = self.auth_config.token

logger.info("")
Expand Down
9 changes: 9 additions & 0 deletions plugins/auth/fps_auth/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ async def get_users(
async def get_api_me(
permissions: Optional[str] = None,
user: UserRead = Depends(backend.current_user()),
update_user = Depends(backend.update_user),
):
checked_permissions: Dict[str, List[str]] = {}
if permissions is None:
Expand All @@ -96,6 +97,14 @@ async def get_api_me(
moon = get_anonymous_username()
identity["name"] = f"Anonymous {moon}"
identity["display_name"] = f"Anonymous {moon}"
identity["initials"] = f"A{moon[0]}"
await update_user(
dict(
name=identity["name"],
display_name=identity["display_name"],
permissions=checked_permissions,
)
)
return {
"identity": identity,
"permissions": checked_permissions,
Expand Down
13 changes: 5 additions & 8 deletions plugins/auth_fief/fps_auth_fief/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from asphalt.core import Component, Context
from asphalt.core import Component, add_resource, get_resource

from jupyverse_api.app import App
from jupyverse_api.auth import Auth, AuthConfig
Expand All @@ -11,13 +11,10 @@ class AuthFiefComponent(Component):
def __init__(self, **kwargs):
self.auth_fief_config = _AuthFiefConfig(**kwargs)

async def start(
self,
ctx: Context,
) -> None:
ctx.add_resource(self.auth_fief_config, types=AuthConfig)
async def start(self) -> None:
add_resource(self.auth_fief_config, types=AuthConfig)

app = await ctx.request_resource(App)
app = await get_resource(App, wait=True)

auth_fief = auth_factory(app, self.auth_fief_config)
ctx.add_resource(auth_fief, types=Auth)
add_resource(auth_fief, types=Auth)
42 changes: 18 additions & 24 deletions plugins/auth_jupyterhub/fps_auth_jupyterhub/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import httpx
from asphalt.core import Component, ContainerComponent, Context, context_teardown
from asphalt.core import (
Component,
ContainerComponent,
add_resource,
get_resource,
start_service_task,
)
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession

from jupyverse_api.app import App
Expand All @@ -11,40 +16,29 @@


class _AuthJupyterHubComponent(Component):
@context_teardown
async def start(
self,
ctx: Context,
) -> None:
app = await ctx.request_resource(App)
db_session = await ctx.request_resource(AsyncSession)
db_engine = await ctx.request_resource(AsyncEngine)

http_client = httpx.AsyncClient()
auth_jupyterhub = auth_factory(app, db_session, http_client)
ctx.add_resource(auth_jupyterhub, types=Auth)
async def start(self) -> None:
app = await get_resource(App, wait=True)
db_session = await get_resource(AsyncSession, wait=True)
db_engine = await get_resource(AsyncEngine, wait=True)

auth_jupyterhub = auth_factory(app, db_session)
await start_service_task(auth_jupyterhub.start, "JupyterHub Auth", auth_jupyterhub.stop)
add_resource(auth_jupyterhub, types=Auth)

async with db_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)

yield

await http_client.aclose()


class AuthJupyterHubComponent(ContainerComponent):
def __init__(self, **kwargs):
self.auth_jupyterhub_config = AuthJupyterHubConfig(**kwargs)
super().__init__()

async def start(
self,
ctx: Context,
) -> None:
ctx.add_resource(self.auth_jupyterhub_config, types=AuthConfig)
async def start(self) -> None:
add_resource(self.auth_jupyterhub_config, types=AuthConfig)
self.add_component(
"sqlalchemy",
url=self.auth_jupyterhub_config.db_url,
)
self.add_component("auth_jupyterhub", type=_AuthJupyterHubComponent)
await super().start(ctx)
await super().start()
27 changes: 18 additions & 9 deletions plugins/auth_jupyterhub/fps_auth_jupyterhub/routes.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from __future__ import annotations

import asyncio
import json
import os
from datetime import datetime
from functools import partial
from typing import Any, Callable, Dict, List, Optional, Tuple, Union

import httpx
from anyio import TASK_STATUS_IGNORED, Lock, create_task_group
from anyio.abc import TaskStatus
from fastapi import APIRouter, Cookie, Depends, HTTPException, Request, WebSocket, status
from fastapi.responses import RedirectResponse
from httpx import AsyncClient
from jupyterhub.services.auth import HubOAuth
from jupyterhub.utils import isoformat
from sqlalchemy.ext.asyncio import AsyncSession
Expand All @@ -26,16 +28,15 @@
def auth_factory(
app: App,
db_session: AsyncSession,
http_client: httpx.AsyncClient,
):
class AuthJupyterHub(Auth, Router):
def __init__(self) -> None:
super().__init__(app)
self.hub_auth = HubOAuth()
self.db_lock = asyncio.Lock()
self.db_lock = Lock()
self.activity_url = os.environ.get("JUPYTERHUB_ACTIVITY_URL")
self.server_name = os.environ.get("JUPYTERHUB_SERVER_NAME")
self.background_tasks = set()
self.http_client = AsyncClient()

router = APIRouter()

Expand Down Expand Up @@ -123,17 +124,16 @@ async def _(
"Content-Type": "application/json",
}
last_activity = isoformat(datetime.utcnow())
task = asyncio.create_task(
http_client.post(
self.task_group.start_soon(
partial(
self.http_client.post,
self.activity_url,
headers=headers,
json={
"servers": {self.server_name: {"last_activity": last_activity}}
},
)
)
self.background_tasks.add(task)
task.add_done_callback(self.background_tasks.discard)
return user

if permissions:
Expand Down Expand Up @@ -193,4 +193,13 @@ async def _(

return _

async def start(self, *, task_status: TaskStatus[None] = TASK_STATUS_IGNORED) -> None:
async with create_task_group() as tg:
self.task_group = tg
task_status.started()

async def stop(self) -> None:
await self.http_client.aclose()
self.task_group.cancel_scope().cancel()

return AuthJupyterHub()
1 change: 1 addition & 0 deletions plugins/auth_jupyterhub/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies = [
"httpx >=0.24.1,<1",
"jupyterhub >=4.0.1,<5",
"jupyverse-api >=0.1.2,<1",
"anyio",
]

[[project.authors]]
Expand Down
Loading

0 comments on commit 0268970

Please sign in to comment.