Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop FPS in favor of Asphalt #277

Merged
merged 8 commits into from
Mar 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/check-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
- name: Install Dependencies
run: |
pip install -e . --no-deps
pip install -e plugins/auth_base
pip install -e jupyverse_api
pip install -e plugins/frontend
pip install -e plugins/jupyterlab
pip install -e plugins/retrolab
Expand All @@ -54,6 +54,8 @@ jobs:
pip install -e plugins/nbconvert
pip install -e plugins/yjs
pip install -e plugins/auth
pip install -e plugins/noauth
pip install -e plugins/auth_fief
pip install -e plugins/login
- name: Check Release
if: ${{ matrix.group == 'check_release' }}
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ jobs:
- name: Upgrade pip
run: python3 -m pip install --upgrade pip

- name: Create jupyterlab-auth dev environment
- name: Create jupyterlab dev environment
run: |
pip install hatch
hatch env create dev.jupyterlab-auth
hatch env create dev.jupyterlab

- name: Check types
run: |
hatch run dev.jupyterlab-auth:typecheck
hatch run dev.jupyterlab:typecheck

- name: Run tests
run: |
hatch run dev.jupyterlab-auth:test
hatch run dev.jupyterlab:test
52 changes: 52 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
component:
type: jupyverse
components:
app:
type: app
auth:
type: auth
#auth:
# type: auth_fief
#auth:
# type: noauth
contents:
type: contents
frontend:
type: frontend
lab:
type: lab
jupyterlab:
type: jupyterlab
kernels:
type: kernels
login:
type: login
nbconvert:
type: nbconvert
resource_usage:
type: resource_usage
track_cpu_percent: true
#retrolab:
# type: retrolab
terminals:
type: terminals
yjs:
type: yjs

logging:
version: 1
disable_existing_loggers: false
formatters:
default:
format: '[%(asctime)s %(levelname)s] %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: default
root:
handlers: [console]
level: INFO
loggers:
webnotifier:
level: DEBUG
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions jupyverse_api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Jupyverse API

The public API for Jupyverse.
42 changes: 42 additions & 0 deletions jupyverse_api/jupyverse_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from typing import Dict

from pydantic import BaseModel, Extra

from .app import App


__version__ = "0.0.50"


class Singleton(type):
_instances: Dict = {}

def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]


class Config(BaseModel):
class Config:
extra = Extra.forbid


class Router:
_app: App

def __init__(
self,
app: App,
) -> None:
self._app = app

@property
def _type(self):
return self.__class__.__name__

def include_router(self, router, **kwargs):
self._app._include_router(router, self._type, **kwargs)

def mount(self, path: str, *args, **kwargs) -> None:
self._app._mount(path, self._type, *args, **kwargs)
51 changes: 51 additions & 0 deletions jupyverse_api/jupyverse_api/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import logging
from collections import defaultdict
from typing import Dict, List

from fastapi import FastAPI

from ..exceptions import RedirectException, _redirect_exception_handler


logger = logging.getLogger("app")


class App:
"""A wrapper around FastAPI that checks for endpoint path conflicts."""

_app: FastAPI
_router_paths: Dict[str, List[str]]

def __init__(self, app: FastAPI):
self._app = app
app.add_exception_handler(RedirectException, _redirect_exception_handler)
self._router_paths = defaultdict(list)

@property
def _paths(self):
return [path for router, paths in self._router_paths.items() for path in paths]

def _include_router(self, router, _type, **kwargs) -> None:
new_paths = []
for route in router.routes:
path = kwargs.get("prefix", "") + route.path
for _router, _paths in self._router_paths.items():
if path in _paths:
raise RuntimeError(
f"{_type} adds a handler for a path that is already defined in "
f"{_router}: {path}"
)
logger.debug("%s added handler for path: %s", _type, path)
new_paths.append(path)
self._router_paths[_type].extend(new_paths)
self._app.include_router(router, **kwargs)

def _mount(self, path: str, _type, *args, **kwargs) -> None:
for _router, _paths in self._router_paths.items():
if path in _paths:
raise RuntimeError(
f"{_type } mounts a path that is already defined in {_router}: {path}"
)
self._router_paths[_type].append(path)
logger.debug("%s mounted path: %s", _type, path)
self._app.mount(path, *args, **kwargs)
27 changes: 27 additions & 0 deletions jupyverse_api/jupyverse_api/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from abc import ABC, abstractmethod
from typing import Any, Callable, Dict, List, Optional, Tuple

from jupyverse_api import Config

from .models import User # noqa


class Auth(ABC):
@abstractmethod
def current_user(self, permissions: Optional[Dict[str, List[str]]] = None) -> Callable:
...

@abstractmethod
async def update_user(self) -> Callable:
...

@abstractmethod
def websocket_auth(
self,
permissions: Optional[Dict[str, List[str]]] = None,
) -> Callable[[], Tuple[Any, Dict[str, List[str]]]]:
...


class AuthConfig(Config):
pass
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
from typing import Optional

from pydantic import BaseModel


class User(BaseModel):
anonymous: bool = True
username: str = ""
name: str = ""
display_name: str = ""
Expand Down
38 changes: 38 additions & 0 deletions jupyverse_api/jupyverse_api/contents/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import asyncio
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Dict, Union

from jupyverse_api import Router

from .models import Content, SaveContent


class FileIdManager(ABC):
stop_watching_files: asyncio.Event
stopped_watching_files: asyncio.Event

@abstractmethod
async def get_path(self, file_id: str) -> str:
...

@abstractmethod
async def get_id(self, file_path: str) -> str:
...


class Contents(Router, ABC):
@property
@abstractmethod
def file_id_manager(self) -> FileIdManager:
...

@abstractmethod
async def read_content(
self, path: Union[str, Path], get_content: bool, as_json: bool = False
) -> Content:
...

@abstractmethod
async def write_content(self, content: Union[SaveContent, Dict]) -> None:
...
23 changes: 23 additions & 0 deletions jupyverse_api/jupyverse_api/contents/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from typing import Dict, List, Optional, Union

from pydantic import BaseModel


class Content(BaseModel):
name: str
path: str
last_modified: Optional[str]
created: Optional[str]
content: Optional[Union[str, Dict, List[Dict]]]
format: Optional[str]
mimetype: Optional[str]
size: Optional[int]
writable: bool
type: str


class SaveContent(BaseModel):
content: Optional[Union[str, Dict]]
format: str
path: str
type: str
11 changes: 11 additions & 0 deletions jupyverse_api/jupyverse_api/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from fastapi import Request, Response
from fastapi.responses import RedirectResponse


class RedirectException(Exception):
def __init__(self, redirect_to: str):
self.redirect_to = redirect_to


async def _redirect_exception_handler(request: Request, exc: RedirectException) -> Response:
return RedirectResponse(url=exc.redirect_to)
6 changes: 6 additions & 0 deletions jupyverse_api/jupyverse_api/frontend/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from jupyverse_api import Config


class FrontendConfig(Config):
base_url: str = "/"
collaborative: bool = False
9 changes: 9 additions & 0 deletions jupyverse_api/jupyverse_api/jupyterlab/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from jupyverse_api import Config, Router


class JupyterLab(Router):
pass


class JupyterLabConfig(Config):
dev_mode: bool = False
16 changes: 16 additions & 0 deletions jupyverse_api/jupyverse_api/kernels/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Optional

from jupyverse_api import Router, Config


class Kernels(Router, ABC):
@abstractmethod
async def watch_connection_files(self, path: Path) -> None:
...


class KernelsConfig(Config):
default_kernel: str = "python3"
connection_path: Optional[str] = None
18 changes: 18 additions & 0 deletions jupyverse_api/jupyverse_api/lab/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any, Dict, List, Tuple

from fastapi import APIRouter
from jupyverse_api import Router


class Lab(Router, ABC):
@abstractmethod
def init_router(
self, router: APIRouter, redirect_after_root: str
) -> Tuple[Path, List[Dict[str, Any]]]:
...

@abstractmethod
def get_federated_extensions(self, extensions_dir: Path) -> Tuple[List, List]:
...
5 changes: 5 additions & 0 deletions jupyverse_api/jupyverse_api/login/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from jupyverse_api import Router


class Login(Router):
pass
24 changes: 24 additions & 0 deletions jupyverse_api/jupyverse_api/main/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from asphalt.core import Component, Context
from asphalt.web.fastapi import FastAPIComponent
from fastapi import FastAPI

from ..app import App


class AppComponent(Component):
async def start(
self,
ctx: Context,
) -> None:
app = await ctx.request_resource(FastAPI)

_app = App(app)
ctx.add_resource(_app)


class JupyverseComponent(FastAPIComponent):
async def start(
self,
ctx: Context,
) -> None:
await super().start(ctx)
5 changes: 5 additions & 0 deletions jupyverse_api/jupyverse_api/nbconvert/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from jupyverse_api import Router


class Nbconvert(Router):
pass
Empty file.
Loading