Skip to content

Commit

Permalink
Features (#2053)
Browse files Browse the repository at this point in the history
* WIP

* new api

* /features endpoint

* New backend API

* Switch to packages in the UI

* Basic UI support for features

* typo

* UI WIP

* Unified markdown handling

* slight fixes

* Properly throw server errors

* Better error messages

* Basic feature checking

* Added spinner while refreshing
  • Loading branch information
RunDevelopment authored Aug 10, 2023
1 parent c4c1370 commit 42b1a92
Show file tree
Hide file tree
Showing 39 changed files with 1,127 additions and 637 deletions.
104 changes: 97 additions & 7 deletions backend/src/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@
import importlib
import os
from dataclasses import dataclass, field
from typing import Callable, Dict, Iterable, List, Tuple, TypedDict, TypeVar
from typing import (
Awaitable,
Callable,
Dict,
Iterable,
List,
NewType,
Tuple,
TypedDict,
TypeVar,
)

from sanic.log import logger

Expand Down Expand Up @@ -83,11 +93,13 @@ class NodeData:
side_effects: bool
deprecated: bool
default_nodes: List[DefaultNode] | None # For iterators only
features: List[FeatureId]

run: RunFn


T = TypeVar("T", bound=RunFn)
S = TypeVar("S")


@dataclass
Expand All @@ -114,14 +126,20 @@ def register(
default_nodes: List[DefaultNode] | None = None,
decorators: List[Callable] | None = None,
see_also: List[str] | str | None = None,
features: List[FeatureId] | FeatureId | None = None,
):
if not isinstance(description, str):
description = "\n\n".join(description)

if see_also is None:
see_also = []
if isinstance(see_also, str):
see_also = [see_also]
def to_list(x: List[S] | S | None) -> List[S]:
if x is None:
return []
if isinstance(x, list):
return x
return [x]

see_also = to_list(see_also)
features = to_list(features)

def run_check(level: CheckLevel, run: Callable[[bool], None]):
if level == CheckLevel.NONE:
Expand Down Expand Up @@ -170,6 +188,7 @@ def inner_wrapper(wrapped_func: T) -> T:
side_effects=side_effects,
deprecated=deprecated,
default_nodes=default_nodes,
features=features,
run=wrapped_func,
)

Expand Down Expand Up @@ -226,13 +245,59 @@ def toDict(self):
}


FeatureId = NewType("FeatureId", str)


@dataclass
class Feature:
id: str
name: str
description: str
behavior: FeatureBehavior | None = None

def add_behavior(self, check: Callable[[], Awaitable[FeatureState]]) -> FeatureId:
if self.behavior is not None:
raise ValueError("Behavior already set")

self.behavior = FeatureBehavior(check=check)
return FeatureId(self.id)

def toDict(self):
return {
"id": self.id,
"name": self.name,
"description": self.description,
}


@dataclass
class FeatureBehavior:
check: Callable[[], Awaitable[FeatureState]]


@dataclass(frozen=True)
class FeatureState:
is_enabled: bool
details: str | None = None

@staticmethod
def enabled(details: str | None = None) -> "FeatureState":
return FeatureState(is_enabled=True, details=details)

@staticmethod
def disabled(details: str | None = None) -> "FeatureState":
return FeatureState(is_enabled=False, details=details)


@dataclass
class Package:
where: str
id: str
name: str
description: str
dependencies: List[Dependency] = field(default_factory=list)
categories: List[Category] = field(default_factory=list)
features: List[Feature] = field(default_factory=list)

def add_category(
self,
Expand All @@ -259,6 +324,19 @@ def add_dependency(
):
self.dependencies.append(dependency)

def add_feature(
self,
id: str, # pylint: disable=redefined-builtin
name: str,
description: str,
) -> Feature:
if any(f.id == id for f in self.features):
raise ValueError(f"Duplicate feature id: {id}")

feature = Feature(id=id, name=name, description=description)
self.features.append(feature)
return feature


def _iter_py_files(directory: str):
for root, _, files in os.walk(directory):
Expand Down Expand Up @@ -331,6 +409,18 @@ def _refresh_nodes(self):


def add_package(
where: str, name: str, description: str, dependencies: List[Dependency]
where: str,
id: str, # pylint: disable=redefined-builtin
name: str,
description: str,
dependencies: List[Dependency] | None = None,
) -> Package:
return registry.add(Package(where, name, description, dependencies))
return registry.add(
Package(
where=where,
id=id,
name=name,
description=description,
dependencies=dependencies or [],
)
)
Loading

0 comments on commit 42b1a92

Please sign in to comment.