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

Switch to FastAIO #453

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
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
93 changes: 50 additions & 43 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 fastaio import get_config, get_root_component, merge_config
from fastaio import main as fastaio_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}")
fastaio_main.callback(
"jupyverse_api.main:JupyverseComponent",
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_component = get_root_component(config)
root_component.run()


def get_config(disable: Tuple[str, ...]) -> str:
def get_pluggin_config(disable: Tuple[str, ...]) -> dict[str, Any]:
jupyverse_components = [
ep.name
for ep in entry_points(group="jupyverse.components")
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_component": {
"components": {
component: {
"type": component
}
for component in jupyverse_components
}
}
}
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
Loading