Skip to content

Commit

Permalink
Switch to FPS
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrochart committed Feb 5, 2025
1 parent 8dc88a4 commit 0efbff4
Show file tree
Hide file tree
Showing 95 changed files with 1,979 additions and 1,761 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.12'
python-version: '3.13'
cache: 'pip'
- name: Install hatch
run: |
Expand All @@ -43,13 +43,13 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ]
python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ]

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
Expand Down
52 changes: 0 additions & 52 deletions config.yaml

This file was deleted.

36 changes: 35 additions & 1 deletion jupyverse_api/jupyverse_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Dict
from typing import Any, Dict

from anyio import Event
from pydantic import BaseModel

from .app import App
Expand Down Expand Up @@ -41,3 +42,36 @@ def mount(self, path: str, *args, **kwargs) -> None:

def add_middleware(self, middleware, *args, **kwargs) -> None:
self._app.add_middleware(middleware, *args, **kwargs)


class ResourceLock:
"""ResourceLock ensures that accesses cannot be done concurrently on the same resource.
"""
_locks: Dict[Any, Event]

def __init__(self):
self._locks = {}

def __call__(self, idx: Any):
return _ResourceLock(idx, self._locks)


class _ResourceLock:
_idx: Any
_locks: Dict[Any, Event]
_lock: Event

def __init__(self, idx: Any, locks: Dict[Any, Event]):
self._idx = idx
self._locks = locks

async def __aenter__(self):
while True:
if self._idx not in self._locks:
break
await self._locks[self._idx].wait()
self._locks[self._idx] = self._lock = Event()

async def __aexit__(self, exc_type, exc_value, exc_tb):
self._lock.set()
del self._locks[self._idx]
8 changes: 4 additions & 4 deletions jupyverse_api/jupyverse_api/app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from __future__ import annotations

import logging
from collections import defaultdict
from datetime import datetime, timezone
from typing import Dict, List

import structlog
from fastapi import FastAPI, Request

from ..exceptions import RedirectException, _redirect_exception_handler

logger = logging.getLogger("app")
logger = structlog.get_logger()


class App:
Expand Down Expand Up @@ -57,7 +57,7 @@ def _include_router(self, router, _type, **kwargs) -> None:
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)
logger.debug("Handler added", type=_type, path=path)
new_paths.append(path)
self._router_paths[_type].extend(new_paths)
self._app.include_router(router, **kwargs)
Expand All @@ -69,7 +69,7 @@ def _mount(self, path: str, _type, *args, **kwargs) -> None:
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)
logger.debug("Path mounted", type=_type, path=path)
self._app.mount(path, *args, **kwargs)

def add_middleware(self, middleware, *args, **kwargs) -> None:
Expand Down
4 changes: 2 additions & 2 deletions jupyverse_api/jupyverse_api/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
from typing import Any, Callable, Dict, List, Optional, Tuple
from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple

from jupyverse_api import Config

Expand All @@ -19,7 +19,7 @@ async def update_user(self) -> Callable:
def websocket_auth(
self,
permissions: Optional[Dict[str, List[str]]] = None,
) -> Callable[[], Tuple[Any, Dict[str, List[str]]]]:
) -> Callable[[Any], Awaitable[Optional[Tuple[Any, Optional[Dict[str, List[str]]]]]]]:
...


Expand Down
97 changes: 52 additions & 45 deletions jupyverse_api/jupyverse_api/cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import json
import sys
from typing import List, Tuple
from typing import Any, List, Tuple

import rich_click as click
from asphalt.core.cli import run
from fps import get_config, get_root_module, merge_config
from fps import main as fps_main

if sys.version_info < (3, 10):
from importlib_metadata import entry_points
Expand All @@ -11,6 +13,13 @@


@click.command() # type: ignore
@click.option(
"--debug",
is_flag=True,
show_default=True,
default=False,
help="Enable debug mode.",
)
@click.option(
"--open-browser",
is_flag=True,
Expand All @@ -30,6 +39,12 @@
default=8000,
help="The host port.",
)
@click.option(
"--query-param",
multiple=True,
type=str,
help='The query parameter key and value, separated by "=".',
)
@click.option(
"--allow-origin",
multiple=True,
Expand All @@ -50,61 +65,53 @@
help="Disable plugin.",
)
def main(
debug: bool = False,
open_browser: bool = False,
host: str = "127.0.0.1",
port: int = 8000,
set_: Tuple[str, ...] = (),
disable: Tuple[str, ...] = (),
allow_origin: Tuple[str, ...] = (),
query_param: Tuple[str, ...] = (),
) -> None:
query_params_dict = {}
for qp in query_param:
key, _, value = qp.partition("=")
query_params_dict[key] = value
query_params_str = json.dumps(query_params_dict)
allow_origins_str = json.dumps(allow_origin)
set_list: List[str] = list(set_)
for i, s in enumerate(set_list):
set_list[i] = f"component.components.{s}"
set_list.append(f"component.open_browser={open_browser}")
set_list.append(f"component.host={host}")
set_list.append(f"component.port={port}")
set_list.append(f"component.allow_origin={allow_origin}")
config = get_config(disable)
run.callback(
unsafe=False,
loop=None,
set_list.append(f"debug={debug}")
set_list.append(f"open_browser={open_browser}")
set_list.append(f"host={host}")
set_list.append(f"port={port}")
set_list.append(f"allow_origins={allow_origins_str}")
set_list.append(f"query_params={query_params_str}")
fps_main.callback(
"jupyverse_api.main:JupyverseModule",
set_=set_list,
service=None,
configfile=[config],
) # type: ignore
cli_config = get_config()
pluggin_config = get_pluggin_config(disable)
config = merge_config(cli_config, pluggin_config)
root_module = get_root_module(config)
root_module.run()


def get_config(disable: Tuple[str, ...]) -> str:
jupyverse_components = [
def get_pluggin_config(disable: Tuple[str, ...]) -> dict[str, Any]:
jupyverse_modules = [
ep.name
for ep in entry_points(group="jupyverse.components")
for ep in entry_points(group="jupyverse.modules")
if ep.name not in disable
]

config = ["component:\n type: jupyverse\n components:\n"]
for component in jupyverse_components:
config.append(f" {component}:\n type: {component}\n")

config.append(
"""
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
"""
)

config_str = "".join(config)
return config_str
config = {
"root_module": {
"modules": {
module: {
"type": module
}
for module in jupyverse_modules
}
}
}
return config
18 changes: 13 additions & 5 deletions jupyverse_api/jupyverse_api/contents/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import asyncio
from __future__ import annotations

from abc import ABC, abstractmethod
from pathlib import Path
from typing import Dict, List, Optional, Union

from fastapi import APIRouter, Depends, Request, Response

from jupyverse_api import Router
from jupyverse_api import ResourceLock, Router

from ..app import App
from ..auth import Auth, User
from .models import Checkpoint, Content, SaveContent


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 All @@ -32,9 +38,11 @@ def unwatch(self, path: str, watcher):


class Contents(Router, ABC):
file_lock: ResourceLock

def __init__(self, app: App, auth: Auth):
super().__init__(app=app)

self.file_lock = ResourceLock()
router = APIRouter()

@router.post(
Expand Down
Loading

0 comments on commit 0efbff4

Please sign in to comment.