-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use htmx-based frontend in fps-spacex
- Loading branch information
1 parent
1b0a518
commit 5274793
Showing
53 changed files
with
1,182 additions
and
234 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
component: | ||
type: jupyspace | ||
components: | ||
app: | ||
type: app | ||
space: | ||
type: space | ||
spacex: | ||
type: spacex | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,4 +3,3 @@ channels: | |
- conda-forge | ||
dependencies: | ||
- pip | ||
- mamba |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1 @@ | ||
# SPDX-FileCopyrightText: 2022-present David Brochart <david.brochart@gmail.com> | ||
# | ||
# SPDX-License-Identifier: MIT | ||
__version__ = "0.0.1" |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Jupyspace API | ||
|
||
The public API for Jupyspace. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from .app import App | ||
from .router import Router | ||
|
||
|
||
__version__ = "0.0.1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
from __future__ import annotations | ||
|
||
import webbrowser | ||
from typing import Any, Callable, Dict, Sequence | ||
|
||
from asgiref.typing import ASGI3Application | ||
from asphalt.core import Component, Context | ||
from asphalt.web.fastapi import FastAPIComponent | ||
from fastapi import FastAPI | ||
from pydantic import BaseModel | ||
|
||
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 JupyspaceComponent(FastAPIComponent): | ||
def __init__( | ||
self, | ||
components: dict[str, dict[str, Any] | None] | None = None, | ||
*, | ||
app: FastAPI | str | None = None, | ||
host: str = "127.0.0.1", | ||
port: int = 8000, | ||
open_browser: bool = False, | ||
query_params: Dict[str, Any] | None = None, | ||
debug: bool | None = None, | ||
middlewares: Sequence[Callable[..., ASGI3Application] | dict[str, Any]] = (), | ||
) -> None: | ||
super().__init__( | ||
components, # type: ignore | ||
app=app, | ||
host=host, | ||
port=port, | ||
debug=debug, | ||
middlewares=middlewares, | ||
) | ||
self.host = host | ||
self.port = port | ||
self.open_browser = open_browser | ||
self.query_params = query_params | ||
|
||
async def start( | ||
self, | ||
ctx: Context, | ||
) -> 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) | ||
|
||
await super().start(ctx) | ||
|
||
# at this point, the server has started | ||
if self.open_browser: | ||
qp = query_params.d | ||
if self.query_params: | ||
qp.update(**self.query_params) | ||
query_params_str = "?" + "&".join([f"{k}={v}" for k, v in qp.items()]) if qp else "" | ||
webbrowser.open_new_tab(f"{self.host}:{self.port}{query_params_str}") | ||
|
||
|
||
class QueryParams(BaseModel): | ||
d: Dict[str, Any] | ||
|
||
|
||
class Host(BaseModel): | ||
url: str |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from .app import App | ||
|
||
|
||
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
from __future__ import annotations | ||
|
||
from abc import ABC, abstractmethod | ||
from typing import List | ||
|
||
from fastapi import APIRouter | ||
|
||
from .models import EnvironmentRead, EnvironmentCreate, ServerRead | ||
from ..app import App | ||
from ..router import Router | ||
|
||
|
||
class Space(Router, ABC): | ||
|
||
def __init__( | ||
self, | ||
app: App, | ||
) -> None: | ||
super().__init__(app) | ||
|
||
router = APIRouter() | ||
|
||
@router.get("/api/environments", response_model=List[EnvironmentRead]) | ||
async def get_environments(): | ||
return await self.get_environments() | ||
|
||
@router.get("/api/environments/{name}", response_model=EnvironmentRead) | ||
async def get_environment(name: str): | ||
return await self.get_environment(name) | ||
|
||
@router.post("/api/environments", response_model=EnvironmentRead, status_code=201) | ||
async def create_environment(environment: EnvironmentCreate): | ||
return await self.create_environment(environment) | ||
|
||
@router.delete("/api/environments/{env_id}") | ||
async def delete_environment(env_id: int): | ||
return await self.delete_environment(env_id) | ||
|
||
@router.get("/api/servers", response_model=List[ServerRead]) | ||
async def get_servers(): | ||
return await self.get_servers() | ||
|
||
@router.post("/api/servers/{env_name}/{cwd:path}", response_model=ServerRead, status_code=201) | ||
async def create_server(env_name, cwd): | ||
return await self.create_server(env_name, cwd) | ||
|
||
@router.get("/api/servers/{server_id}", response_model=ServerRead) | ||
async def get_server(server_id: int): | ||
return await self.get_server(server_id) | ||
|
||
@router.delete("/api/servers/{server_id}") | ||
async def stop_server(server_id: int): | ||
return await self.stop_server(server_id) | ||
|
||
self.include_router(router) | ||
|
||
@abstractmethod | ||
async def get_environments(self): | ||
... | ||
|
||
@abstractmethod | ||
async def get_environment(self, name: str): | ||
... | ||
|
||
@abstractmethod | ||
async def create_environment(self, environment: EnvironmentCreate): | ||
... | ||
|
||
@abstractmethod | ||
async def delete_environment(self, env_id: int): | ||
... | ||
|
||
@abstractmethod | ||
async def get_servers(self): | ||
... | ||
|
||
@abstractmethod | ||
async def create_server(self, env_name: str): | ||
... | ||
|
||
@abstractmethod | ||
async def get_server(self, server_id: int): | ||
... | ||
|
||
@abstractmethod | ||
async def stop_server(self, server_id: int): | ||
... |
Oops, something went wrong.