Spaces:
Running
Running
Set up a "workspace" package, fix up dependencies, set up Deptry.
Browse files- .pre-commit-config.yaml +15 -0
- docs/contributing.md +4 -5
- lynxkite-app/pyproject.toml +16 -5
- lynxkite-app/src/lynxkite_app/main.py +5 -1
- lynxkite-core/pyproject.toml +11 -6
- lynxkite-core/src/lynxkite/core/executors/one_by_one.py +4 -38
- lynxkite-core/src/lynxkite/core/ops.py +18 -13
- lynxkite-core/src/lynxkite/core/workspace.py +6 -4
- lynxkite-graph-analytics/pyproject.toml +23 -6
- lynxkite-pillow-example/pyproject.toml +13 -3
- pyproject.toml +33 -0
- test.sh +0 -6
.pre-commit-config.yaml
CHANGED
|
@@ -25,3 +25,18 @@ repos:
|
|
| 25 |
pass_filenames: false
|
| 26 |
args: [--python=.venv/]
|
| 27 |
additional_dependencies: [ty]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
pass_filenames: false
|
| 26 |
args: [--python=.venv/]
|
| 27 |
additional_dependencies: [ty]
|
| 28 |
+
- repo: https://github.com/fpgmaas/deptry.git
|
| 29 |
+
rev: "0.23.0"
|
| 30 |
+
hooks:
|
| 31 |
+
- id: deptry
|
| 32 |
+
name: deptry for lynxkite-app
|
| 33 |
+
entry: bash -c 'cd lynxkite-app && deptry .'
|
| 34 |
+
- id: deptry
|
| 35 |
+
name: deptry for lynxkite-core
|
| 36 |
+
entry: bash -c 'cd lynxkite-core && deptry .'
|
| 37 |
+
- id: deptry
|
| 38 |
+
name: deptry for lynxkite-graph-analytics
|
| 39 |
+
entry: bash -c 'cd lynxkite-graph-analytics && deptry .'
|
| 40 |
+
- id: deptry
|
| 41 |
+
name: deptry for lynxkite-pillow-example
|
| 42 |
+
entry: bash -c 'cd lynxkite-pillow-example && deptry .'
|
docs/contributing.md
CHANGED
|
@@ -19,7 +19,7 @@ Install everything like this:
|
|
| 19 |
uv venv
|
| 20 |
source .venv/bin/activate
|
| 21 |
uvx pre-commit install
|
| 22 |
-
uv
|
| 23 |
```
|
| 24 |
|
| 25 |
This also builds the frontend, hopefully very quickly. To run it:
|
|
@@ -38,10 +38,10 @@ npm run dev
|
|
| 38 |
|
| 39 |
## Executing tests
|
| 40 |
|
| 41 |
-
Run all tests with a single command, or look inside to see how to run them individually:
|
| 42 |
-
|
| 43 |
```bash
|
| 44 |
-
|
|
|
|
|
|
|
| 45 |
```
|
| 46 |
|
| 47 |
## Documentation
|
|
@@ -49,6 +49,5 @@ Run all tests with a single command, or look inside to see how to run them indiv
|
|
| 49 |
To work on the documentation:
|
| 50 |
|
| 51 |
```bash
|
| 52 |
-
uv pip install mkdocs-material mkdocstrings[python]
|
| 53 |
mkdocs serve
|
| 54 |
```
|
|
|
|
| 19 |
uv venv
|
| 20 |
source .venv/bin/activate
|
| 21 |
uvx pre-commit install
|
| 22 |
+
uv sync
|
| 23 |
```
|
| 24 |
|
| 25 |
This also builds the frontend, hopefully very quickly. To run it:
|
|
|
|
| 38 |
|
| 39 |
## Executing tests
|
| 40 |
|
|
|
|
|
|
|
| 41 |
```bash
|
| 42 |
+
pytest # Runs all backend unit tests.
|
| 43 |
+
pytest lynxkite-core # Runs tests for one package.
|
| 44 |
+
cd lynxkite-app/web && npm run test # Runs frontend tests.
|
| 45 |
```
|
| 46 |
|
| 47 |
## Documentation
|
|
|
|
| 49 |
To work on the documentation:
|
| 50 |
|
| 51 |
```bash
|
|
|
|
| 52 |
mkdocs serve
|
| 53 |
```
|
lynxkite-app/pyproject.toml
CHANGED
|
@@ -6,25 +6,28 @@ readme = "README.md"
|
|
| 6 |
requires-python = ">=3.11"
|
| 7 |
dependencies = [
|
| 8 |
"fastapi[standard]>=0.115.6",
|
|
|
|
|
|
|
| 9 |
"lynxkite-core",
|
| 10 |
-
"orjson>=3.10.13",
|
| 11 |
"pycrdt-websocket>=0.16",
|
|
|
|
|
|
|
| 12 |
"sse-starlette>=2.2.1",
|
| 13 |
-
"
|
| 14 |
]
|
| 15 |
classifiers = ["License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)"]
|
| 16 |
|
| 17 |
[project.urls]
|
| 18 |
Homepage = "https://github.com/lynxkite/lynxkite-2000/"
|
| 19 |
|
| 20 |
-
[
|
| 21 |
dev = [
|
| 22 |
"pydantic-to-typescript>=2.0.0",
|
| 23 |
-
"
|
| 24 |
]
|
| 25 |
|
| 26 |
[tool.uv.sources]
|
| 27 |
-
lynxkite-core = {
|
| 28 |
|
| 29 |
[build-system]
|
| 30 |
requires = ["setuptools", "wheel", "setuptools-scm"]
|
|
@@ -47,3 +50,11 @@ build_py = "build_frontend.build_py"
|
|
| 47 |
|
| 48 |
[project.scripts]
|
| 49 |
lynxkite = "lynxkite_app.__main__:main"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
requires-python = ">=3.11"
|
| 7 |
dependencies = [
|
| 8 |
"fastapi[standard]>=0.115.6",
|
| 9 |
+
"griffe>=1.7.3",
|
| 10 |
+
"joblib>=1.5.1",
|
| 11 |
"lynxkite-core",
|
|
|
|
| 12 |
"pycrdt-websocket>=0.16",
|
| 13 |
+
"pycrdt>=0.12.26",
|
| 14 |
+
"pydantic>=2.11.7",
|
| 15 |
"sse-starlette>=2.2.1",
|
| 16 |
+
"uvicorn>=0.35.0",
|
| 17 |
]
|
| 18 |
classifiers = ["License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)"]
|
| 19 |
|
| 20 |
[project.urls]
|
| 21 |
Homepage = "https://github.com/lynxkite/lynxkite-2000/"
|
| 22 |
|
| 23 |
+
[dependency-groups]
|
| 24 |
dev = [
|
| 25 |
"pydantic-to-typescript>=2.0.0",
|
| 26 |
+
"setuptools>=80.9.0",
|
| 27 |
]
|
| 28 |
|
| 29 |
[tool.uv.sources]
|
| 30 |
+
lynxkite-core = { workspace = true }
|
| 31 |
|
| 32 |
[build-system]
|
| 33 |
requires = ["setuptools", "wheel", "setuptools-scm"]
|
|
|
|
| 50 |
|
| 51 |
[project.scripts]
|
| 52 |
lynxkite = "lynxkite_app.__main__:main"
|
| 53 |
+
|
| 54 |
+
[tool.deptry.package_module_name_map]
|
| 55 |
+
lynxkite-core = "lynxkite"
|
| 56 |
+
sse-starlette = "starlette"
|
| 57 |
+
|
| 58 |
+
[tool.deptry.per_rule_ignores]
|
| 59 |
+
DEP002 = ["pycrdt-websocket", "griffe"]
|
| 60 |
+
DEP004 = ["setuptools"]
|
lynxkite-app/src/lynxkite_app/main.py
CHANGED
|
@@ -4,6 +4,7 @@ import shutil
|
|
| 4 |
import pydantic
|
| 5 |
import fastapi
|
| 6 |
import importlib
|
|
|
|
| 7 |
import pathlib
|
| 8 |
import pkgutil
|
| 9 |
from fastapi.staticfiles import StaticFiles
|
|
@@ -13,11 +14,14 @@ from lynxkite.core import ops
|
|
| 13 |
from lynxkite.core import workspace
|
| 14 |
from . import crdt
|
| 15 |
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
def detect_plugins():
|
| 18 |
plugins = {}
|
| 19 |
for _, name, _ in pkgutil.iter_modules():
|
| 20 |
-
if name.startswith("lynxkite_"):
|
| 21 |
print(f"Importing {name}")
|
| 22 |
plugins[name] = importlib.import_module(name)
|
| 23 |
if not plugins:
|
|
|
|
| 4 |
import pydantic
|
| 5 |
import fastapi
|
| 6 |
import importlib
|
| 7 |
+
import joblib
|
| 8 |
import pathlib
|
| 9 |
import pkgutil
|
| 10 |
from fastapi.staticfiles import StaticFiles
|
|
|
|
| 14 |
from lynxkite.core import workspace
|
| 15 |
from . import crdt
|
| 16 |
|
| 17 |
+
mem = joblib.Memory(".joblib-cache")
|
| 18 |
+
ops.CACHE_WRAPPER = mem.cache
|
| 19 |
+
|
| 20 |
|
| 21 |
def detect_plugins():
|
| 22 |
plugins = {}
|
| 23 |
for _, name, _ in pkgutil.iter_modules():
|
| 24 |
+
if name.startswith("lynxkite_") and name != "lynxkite_app":
|
| 25 |
print(f"Importing {name}")
|
| 26 |
plugins[name] = importlib.import_module(name)
|
| 27 |
if not plugins:
|
lynxkite-core/pyproject.toml
CHANGED
|
@@ -5,16 +5,21 @@ description = "A lightweight dependency for authoring LynxKite operations and ex
|
|
| 5 |
readme = "README.md"
|
| 6 |
requires-python = ">=3.11"
|
| 7 |
dependencies = [
|
|
|
|
| 8 |
]
|
| 9 |
classifiers = ["License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)"]
|
| 10 |
|
| 11 |
[project.urls]
|
| 12 |
Homepage = "https://github.com/lynxkite/lynxkite-2000/"
|
| 13 |
|
| 14 |
-
[
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
[tool.
|
| 20 |
-
|
|
|
|
|
|
| 5 |
readme = "README.md"
|
| 6 |
requires-python = ">=3.11"
|
| 7 |
dependencies = [
|
| 8 |
+
"pydantic>=2.11.7",
|
| 9 |
]
|
| 10 |
classifiers = ["License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)"]
|
| 11 |
|
| 12 |
[project.urls]
|
| 13 |
Homepage = "https://github.com/lynxkite/lynxkite-2000/"
|
| 14 |
|
| 15 |
+
[tool.deptry.per_rule_ignores]
|
| 16 |
+
DEP001 = ["matplotlib", "griffe", "pycrdt"]
|
| 17 |
+
DEP003 = ["matplotlib", "griffe", "pycrdt"]
|
| 18 |
+
|
| 19 |
+
[build-system]
|
| 20 |
+
requires = ["setuptools", "wheel", "setuptools-scm"]
|
| 21 |
+
build-backend = "setuptools.build_meta"
|
| 22 |
|
| 23 |
+
[tool.setuptools.packages.find]
|
| 24 |
+
namespaces = true
|
| 25 |
+
where = ["src"]
|
lynxkite-core/src/lynxkite/core/executors/one_by_one.py
CHANGED
|
@@ -4,9 +4,6 @@ A LynxKite executor that assumes most operations operate on their input one by o
|
|
| 4 |
|
| 5 |
from .. import ops
|
| 6 |
from .. import workspace
|
| 7 |
-
import orjson
|
| 8 |
-
import pandas as pd
|
| 9 |
-
import pydantic
|
| 10 |
import traceback
|
| 11 |
import inspect
|
| 12 |
import typing
|
|
@@ -35,9 +32,6 @@ def _has_ctx(op):
|
|
| 35 |
return "_ctx" in sig.parameters
|
| 36 |
|
| 37 |
|
| 38 |
-
CACHES = {}
|
| 39 |
-
|
| 40 |
-
|
| 41 |
def register(env: str, cache: bool = True):
|
| 42 |
"""Registers the one-by-one executor.
|
| 43 |
|
|
@@ -46,12 +40,7 @@ def register(env: str, cache: bool = True):
|
|
| 46 |
from lynxkite.core.executors import one_by_one
|
| 47 |
one_by_one.register("My Environment")
|
| 48 |
"""
|
| 49 |
-
|
| 50 |
-
CACHES[env] = {}
|
| 51 |
-
_cache = CACHES[env]
|
| 52 |
-
else:
|
| 53 |
-
_cache = None
|
| 54 |
-
ops.EXECUTORS[env] = lambda ws: _execute(ws, ops.CATALOGS[env], cache=_cache)
|
| 55 |
|
| 56 |
|
| 57 |
def _get_stages(ws, catalog: ops.Catalog):
|
|
@@ -83,28 +72,13 @@ def _get_stages(ws, catalog: ops.Catalog):
|
|
| 83 |
return stages
|
| 84 |
|
| 85 |
|
| 86 |
-
def _default_serializer(obj):
|
| 87 |
-
if isinstance(obj, pydantic.BaseModel):
|
| 88 |
-
return obj.dict()
|
| 89 |
-
return {"__nonserializable__": id(obj)}
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
def _make_cache_key(obj):
|
| 93 |
-
return orjson.dumps(obj, default=_default_serializer)
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
EXECUTOR_OUTPUT_CACHE = {}
|
| 97 |
-
|
| 98 |
-
|
| 99 |
async def _await_if_needed(obj):
|
| 100 |
if inspect.isawaitable(obj):
|
| 101 |
return await obj
|
| 102 |
return obj
|
| 103 |
|
| 104 |
|
| 105 |
-
async def _execute(
|
| 106 |
-
ws: workspace.Workspace, catalog: ops.Catalog, cache: typing.Optional[dict] = None
|
| 107 |
-
):
|
| 108 |
nodes = {n.id: n for n in ws.nodes}
|
| 109 |
contexts = {n.id: Context(node=n) for n in ws.nodes}
|
| 110 |
edges = {n.id: [] for n in ws.nodes}
|
|
@@ -157,15 +131,7 @@ async def _execute(
|
|
| 157 |
if missing:
|
| 158 |
node.publish_error(f"Missing input: {', '.join(missing)}")
|
| 159 |
break
|
| 160 |
-
|
| 161 |
-
key = _make_cache_key((inputs, params))
|
| 162 |
-
if key not in cache:
|
| 163 |
-
result: ops.Result = op(*inputs, **params)
|
| 164 |
-
result.output = await _await_if_needed(result.output)
|
| 165 |
-
cache[key] = result
|
| 166 |
-
result = cache[key]
|
| 167 |
-
else:
|
| 168 |
-
result = op(*inputs, **params)
|
| 169 |
output = await _await_if_needed(result.output)
|
| 170 |
except Exception as e:
|
| 171 |
traceback.print_exc()
|
|
@@ -173,7 +139,7 @@ async def _execute(
|
|
| 173 |
break
|
| 174 |
contexts[node.id].last_result = output
|
| 175 |
# Returned lists and DataFrames are considered multiple tasks.
|
| 176 |
-
if
|
| 177 |
output = _df_to_list(output)
|
| 178 |
elif not isinstance(output, list):
|
| 179 |
output = [output]
|
|
|
|
| 4 |
|
| 5 |
from .. import ops
|
| 6 |
from .. import workspace
|
|
|
|
|
|
|
|
|
|
| 7 |
import traceback
|
| 8 |
import inspect
|
| 9 |
import typing
|
|
|
|
| 32 |
return "_ctx" in sig.parameters
|
| 33 |
|
| 34 |
|
|
|
|
|
|
|
|
|
|
| 35 |
def register(env: str, cache: bool = True):
|
| 36 |
"""Registers the one-by-one executor.
|
| 37 |
|
|
|
|
| 40 |
from lynxkite.core.executors import one_by_one
|
| 41 |
one_by_one.register("My Environment")
|
| 42 |
"""
|
| 43 |
+
ops.EXECUTORS[env] = lambda ws: _execute(ws, ops.CATALOGS[env])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
|
| 46 |
def _get_stages(ws, catalog: ops.Catalog):
|
|
|
|
| 72 |
return stages
|
| 73 |
|
| 74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
async def _await_if_needed(obj):
|
| 76 |
if inspect.isawaitable(obj):
|
| 77 |
return await obj
|
| 78 |
return obj
|
| 79 |
|
| 80 |
|
| 81 |
+
async def _execute(ws: workspace.Workspace, catalog: ops.Catalog):
|
|
|
|
|
|
|
| 82 |
nodes = {n.id: n for n in ws.nodes}
|
| 83 |
contexts = {n.id: Context(node=n) for n in ws.nodes}
|
| 84 |
edges = {n.id: [] for n in ws.nodes}
|
|
|
|
| 131 |
if missing:
|
| 132 |
node.publish_error(f"Missing input: {', '.join(missing)}")
|
| 133 |
break
|
| 134 |
+
result = op(*inputs, **params)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
output = await _await_if_needed(result.output)
|
| 136 |
except Exception as e:
|
| 137 |
traceback.print_exc()
|
|
|
|
| 139 |
break
|
| 140 |
contexts[node.id].last_result = output
|
| 141 |
# Returned lists and DataFrames are considered multiple tasks.
|
| 142 |
+
if hasattr(output, "to_dict"):
|
| 143 |
output = _df_to_list(output)
|
| 144 |
elif not isinstance(output, list):
|
| 145 |
output = [output]
|
lynxkite-core/src/lynxkite/core/ops.py
CHANGED
|
@@ -15,9 +15,7 @@ import types
|
|
| 15 |
import typing
|
| 16 |
from dataclasses import dataclass
|
| 17 |
|
| 18 |
-
import joblib
|
| 19 |
import pydantic
|
| 20 |
-
from typing_extensions import Annotated
|
| 21 |
|
| 22 |
if typing.TYPE_CHECKING:
|
| 23 |
from . import workspace
|
|
@@ -26,10 +24,17 @@ Catalog = dict[str, "Op"]
|
|
| 26 |
Catalogs = dict[str, Catalog]
|
| 27 |
CATALOGS: Catalogs = {}
|
| 28 |
EXECUTORS = {}
|
| 29 |
-
mem = joblib.Memory(".joblib-cache")
|
| 30 |
|
| 31 |
typeof = type # We have some arguments called "type".
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
def type_to_json(t):
|
| 35 |
if isinstance(t, type) and issubclass(t, enum.Enum):
|
|
@@ -39,10 +44,10 @@ def type_to_json(t):
|
|
| 39 |
return {"type": str(t)}
|
| 40 |
|
| 41 |
|
| 42 |
-
Type = Annotated[typing.Any, pydantic.PlainSerializer(type_to_json, return_type=dict)]
|
| 43 |
-
LongStr = Annotated[str, {"format": "textarea"}]
|
| 44 |
"""LongStr is a string type for parameters that will be displayed as a multiline text area in the UI."""
|
| 45 |
-
PathStr = Annotated[str, {"format": "path"}]
|
| 46 |
# https://github.com/python/typing/issues/182#issuecomment-1320974824
|
| 47 |
ReadOnlyJSON: typing.TypeAlias = (
|
| 48 |
typing.Mapping[str, "ReadOnlyJSON"]
|
|
@@ -239,12 +244,12 @@ class Op(BaseConfig):
|
|
| 239 |
def op(
|
| 240 |
env: str,
|
| 241 |
*names: str,
|
| 242 |
-
view="basic",
|
| 243 |
-
outputs=None,
|
| 244 |
-
params=None,
|
| 245 |
-
slow=False,
|
| 246 |
-
color=None,
|
| 247 |
-
cache=None,
|
| 248 |
):
|
| 249 |
"""
|
| 250 |
Decorator for defining an operation.
|
|
@@ -275,7 +280,7 @@ def op(
|
|
| 275 |
if slow:
|
| 276 |
func = make_async(func)
|
| 277 |
if cache is not False:
|
| 278 |
-
func =
|
| 279 |
# Positional arguments are inputs.
|
| 280 |
inputs = [
|
| 281 |
Input(name=name, type=param.annotation)
|
|
|
|
| 15 |
import typing
|
| 16 |
from dataclasses import dataclass
|
| 17 |
|
|
|
|
| 18 |
import pydantic
|
|
|
|
| 19 |
|
| 20 |
if typing.TYPE_CHECKING:
|
| 21 |
from . import workspace
|
|
|
|
| 24 |
Catalogs = dict[str, Catalog]
|
| 25 |
CATALOGS: Catalogs = {}
|
| 26 |
EXECUTORS = {}
|
|
|
|
| 27 |
|
| 28 |
typeof = type # We have some arguments called "type".
|
| 29 |
|
| 30 |
+
CACHE_WRAPPER = None # Overwrite this to configure a caching mechanism.
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _cache_wrap(func):
|
| 34 |
+
if CACHE_WRAPPER is None:
|
| 35 |
+
return func
|
| 36 |
+
return CACHE_WRAPPER(func)
|
| 37 |
+
|
| 38 |
|
| 39 |
def type_to_json(t):
|
| 40 |
if isinstance(t, type) and issubclass(t, enum.Enum):
|
|
|
|
| 44 |
return {"type": str(t)}
|
| 45 |
|
| 46 |
|
| 47 |
+
Type = typing.Annotated[typing.Any, pydantic.PlainSerializer(type_to_json, return_type=dict)]
|
| 48 |
+
LongStr = typing.Annotated[str, {"format": "textarea"}]
|
| 49 |
"""LongStr is a string type for parameters that will be displayed as a multiline text area in the UI."""
|
| 50 |
+
PathStr = typing.Annotated[str, {"format": "path"}]
|
| 51 |
# https://github.com/python/typing/issues/182#issuecomment-1320974824
|
| 52 |
ReadOnlyJSON: typing.TypeAlias = (
|
| 53 |
typing.Mapping[str, "ReadOnlyJSON"]
|
|
|
|
| 244 |
def op(
|
| 245 |
env: str,
|
| 246 |
*names: str,
|
| 247 |
+
view: str = "basic",
|
| 248 |
+
outputs: list[str] | None = None,
|
| 249 |
+
params: list[Parameter] | None = None,
|
| 250 |
+
slow: bool = False,
|
| 251 |
+
color: str | None = None,
|
| 252 |
+
cache: bool | None = None,
|
| 253 |
):
|
| 254 |
"""
|
| 255 |
Decorator for defining an operation.
|
|
|
|
| 280 |
if slow:
|
| 281 |
func = make_async(func)
|
| 282 |
if cache is not False:
|
| 283 |
+
func = _cache_wrap(func)
|
| 284 |
# Positional arguments are inputs.
|
| 285 |
inputs = [
|
| 286 |
Input(name=name, type=param.annotation)
|
lynxkite-core/src/lynxkite/core/workspace.py
CHANGED
|
@@ -5,12 +5,12 @@ from typing import Optional, TYPE_CHECKING
|
|
| 5 |
import dataclasses
|
| 6 |
import enum
|
| 7 |
import os
|
| 8 |
-
import pycrdt
|
| 9 |
import pydantic
|
| 10 |
import tempfile
|
| 11 |
from . import ops
|
| 12 |
|
| 13 |
if TYPE_CHECKING:
|
|
|
|
| 14 |
from lynxkite.core import ops
|
| 15 |
|
| 16 |
|
|
@@ -65,7 +65,7 @@ class WorkspaceNode(BaseConfig):
|
|
| 65 |
position: Position
|
| 66 |
width: Optional[float] = None
|
| 67 |
height: Optional[float] = None
|
| 68 |
-
_crdt: Optional[pycrdt.Map] = None
|
| 69 |
|
| 70 |
def publish_started(self):
|
| 71 |
"""Notifies the frontend that work has started on this node."""
|
|
@@ -118,7 +118,7 @@ class Workspace(BaseConfig):
|
|
| 118 |
env: str = ""
|
| 119 |
nodes: list[WorkspaceNode] = dataclasses.field(default_factory=list)
|
| 120 |
edges: list[WorkspaceEdge] = dataclasses.field(default_factory=list)
|
| 121 |
-
_crdt: Optional[pycrdt.Map] = None
|
| 122 |
|
| 123 |
def normalize(self):
|
| 124 |
if self.env not in ops.CATALOGS:
|
|
@@ -219,7 +219,9 @@ class Workspace(BaseConfig):
|
|
| 219 |
node._crdt["data"]["meta"] = {}
|
| 220 |
node._crdt["data"]["error"] = "Unknown operation."
|
| 221 |
|
| 222 |
-
def connect_crdt(self, ws_crdt: pycrdt.Map):
|
|
|
|
|
|
|
| 223 |
self._crdt = ws_crdt
|
| 224 |
with ws_crdt.doc.transaction():
|
| 225 |
for nc, np in zip(ws_crdt["nodes"], self.nodes):
|
|
|
|
| 5 |
import dataclasses
|
| 6 |
import enum
|
| 7 |
import os
|
|
|
|
| 8 |
import pydantic
|
| 9 |
import tempfile
|
| 10 |
from . import ops
|
| 11 |
|
| 12 |
if TYPE_CHECKING:
|
| 13 |
+
import pycrdt
|
| 14 |
from lynxkite.core import ops
|
| 15 |
|
| 16 |
|
|
|
|
| 65 |
position: Position
|
| 66 |
width: Optional[float] = None
|
| 67 |
height: Optional[float] = None
|
| 68 |
+
_crdt: Optional["pycrdt.Map"] = None
|
| 69 |
|
| 70 |
def publish_started(self):
|
| 71 |
"""Notifies the frontend that work has started on this node."""
|
|
|
|
| 118 |
env: str = ""
|
| 119 |
nodes: list[WorkspaceNode] = dataclasses.field(default_factory=list)
|
| 120 |
edges: list[WorkspaceEdge] = dataclasses.field(default_factory=list)
|
| 121 |
+
_crdt: Optional["pycrdt.Map"] = None
|
| 122 |
|
| 123 |
def normalize(self):
|
| 124 |
if self.env not in ops.CATALOGS:
|
|
|
|
| 219 |
node._crdt["data"]["meta"] = {}
|
| 220 |
node._crdt["data"]["error"] = "Unknown operation."
|
| 221 |
|
| 222 |
+
def connect_crdt(self, ws_crdt: "pycrdt.Map"):
|
| 223 |
+
import pycrdt
|
| 224 |
+
|
| 225 |
self._crdt = ws_crdt
|
| 226 |
with ws_crdt.doc.transaction():
|
| 227 |
for nc, np in zip(ws_crdt["nodes"], self.nodes):
|
lynxkite-graph-analytics/pyproject.toml
CHANGED
|
@@ -5,20 +5,23 @@ description = "The graph analytics executor and boxes for LynxKite"
|
|
| 5 |
readme = "README.md"
|
| 6 |
requires-python = ">=3.11"
|
| 7 |
dependencies = [
|
|
|
|
| 8 |
"fsspec>=2025.3.2",
|
| 9 |
"grand-cypher>=0.13.0",
|
| 10 |
-
"joblib>=1.4.2",
|
| 11 |
"lynxkite-core",
|
| 12 |
"matplotlib>=3.10.1",
|
| 13 |
"networkx[default]>=3.4.2",
|
| 14 |
"numba>=0.61.2",
|
|
|
|
| 15 |
"osmnx>=2.0.2",
|
| 16 |
"pandas>=2.2.3",
|
| 17 |
"polars>=1.25.2",
|
| 18 |
"pyarrow>=19.0.1",
|
|
|
|
| 19 |
"torch>=2.7.0",
|
| 20 |
"torch-geometric>=2.6.1",
|
| 21 |
"torchdiffeq>=0.2.5",
|
|
|
|
| 22 |
"umap-learn>=0.5.9.post2",
|
| 23 |
]
|
| 24 |
classifiers = ["License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)"]
|
|
@@ -27,10 +30,6 @@ classifiers = ["License :: OSI Approved :: GNU Affero General Public License v3
|
|
| 27 |
Homepage = "https://github.com/lynxkite/lynxkite-2000/"
|
| 28 |
|
| 29 |
[project.optional-dependencies]
|
| 30 |
-
dev = [
|
| 31 |
-
"pytest>=8.3.5",
|
| 32 |
-
"pytest-asyncio>=0.26.0",
|
| 33 |
-
]
|
| 34 |
gpu = [
|
| 35 |
"cuml-cu12>=25.2.1",
|
| 36 |
"nx-cugraph-cu12>=25.4.0",
|
|
@@ -39,7 +38,7 @@ gpu = [
|
|
| 39 |
]
|
| 40 |
|
| 41 |
[tool.uv.sources]
|
| 42 |
-
lynxkite-core = {
|
| 43 |
pylibcugraph-cu12 = { index = "nvidia" }
|
| 44 |
|
| 45 |
[tool.pytest.ini_options]
|
|
@@ -48,3 +47,21 @@ asyncio_mode = "auto"
|
|
| 48 |
[[tool.uv.index]]
|
| 49 |
name = "nvidia"
|
| 50 |
url = "https://pypi.nvidia.com"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
readme = "README.md"
|
| 6 |
requires-python = ">=3.11"
|
| 7 |
dependencies = [
|
| 8 |
+
"cudf-cu12>=25.6.0",
|
| 9 |
"fsspec>=2025.3.2",
|
| 10 |
"grand-cypher>=0.13.0",
|
|
|
|
| 11 |
"lynxkite-core",
|
| 12 |
"matplotlib>=3.10.1",
|
| 13 |
"networkx[default]>=3.4.2",
|
| 14 |
"numba>=0.61.2",
|
| 15 |
+
"numpy>=2.2.6",
|
| 16 |
"osmnx>=2.0.2",
|
| 17 |
"pandas>=2.2.3",
|
| 18 |
"polars>=1.25.2",
|
| 19 |
"pyarrow>=19.0.1",
|
| 20 |
+
"pydantic>=2.11.7",
|
| 21 |
"torch>=2.7.0",
|
| 22 |
"torch-geometric>=2.6.1",
|
| 23 |
"torchdiffeq>=0.2.5",
|
| 24 |
+
"tqdm>=4.67.1",
|
| 25 |
"umap-learn>=0.5.9.post2",
|
| 26 |
]
|
| 27 |
classifiers = ["License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)"]
|
|
|
|
| 30 |
Homepage = "https://github.com/lynxkite/lynxkite-2000/"
|
| 31 |
|
| 32 |
[project.optional-dependencies]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
gpu = [
|
| 34 |
"cuml-cu12>=25.2.1",
|
| 35 |
"nx-cugraph-cu12>=25.4.0",
|
|
|
|
| 38 |
]
|
| 39 |
|
| 40 |
[tool.uv.sources]
|
| 41 |
+
lynxkite-core = { workspace = true }
|
| 42 |
pylibcugraph-cu12 = { index = "nvidia" }
|
| 43 |
|
| 44 |
[tool.pytest.ini_options]
|
|
|
|
| 47 |
[[tool.uv.index]]
|
| 48 |
name = "nvidia"
|
| 49 |
url = "https://pypi.nvidia.com"
|
| 50 |
+
|
| 51 |
+
[tool.deptry.per_rule_ignores]
|
| 52 |
+
DEP002 = ["numba", "pyarrow", "nx-cugraph-cu12", "pylibcugraph-cu12"]
|
| 53 |
+
|
| 54 |
+
[tool.deptry.package_module_name_map]
|
| 55 |
+
grand-cypher = "grandcypher"
|
| 56 |
+
lynxkite-core = "lynxkite"
|
| 57 |
+
umap-learn = "umap"
|
| 58 |
+
cuml-cu12 = "cuml"
|
| 59 |
+
cudf-cu12 = "cudf"
|
| 60 |
+
|
| 61 |
+
[build-system]
|
| 62 |
+
requires = ["setuptools", "wheel", "setuptools-scm"]
|
| 63 |
+
build-backend = "setuptools.build_meta"
|
| 64 |
+
|
| 65 |
+
[tool.setuptools.packages.find]
|
| 66 |
+
namespaces = true
|
| 67 |
+
where = ["src"]
|
lynxkite-pillow-example/pyproject.toml
CHANGED
|
@@ -8,8 +8,6 @@ dependencies = [
|
|
| 8 |
"fsspec>=2025.2.0",
|
| 9 |
"lynxkite-core",
|
| 10 |
"pillow>=11.1.0",
|
| 11 |
-
"requests>=2.32.3",
|
| 12 |
-
"aiohttp>=3.11.11",
|
| 13 |
]
|
| 14 |
classifiers = ["License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)"]
|
| 15 |
|
|
@@ -17,4 +15,16 @@ classifiers = ["License :: OSI Approved :: GNU Affero General Public License v3
|
|
| 17 |
Homepage = "https://github.com/lynxkite/lynxkite-2000/"
|
| 18 |
|
| 19 |
[tool.uv.sources]
|
| 20 |
-
lynxkite-core = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
"fsspec>=2025.2.0",
|
| 9 |
"lynxkite-core",
|
| 10 |
"pillow>=11.1.0",
|
|
|
|
|
|
|
| 11 |
]
|
| 12 |
classifiers = ["License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)"]
|
| 13 |
|
|
|
|
| 15 |
Homepage = "https://github.com/lynxkite/lynxkite-2000/"
|
| 16 |
|
| 17 |
[tool.uv.sources]
|
| 18 |
+
lynxkite-core = { workspace = true }
|
| 19 |
+
|
| 20 |
+
[tool.deptry.package_module_name_map]
|
| 21 |
+
lynxkite-core = "lynxkite"
|
| 22 |
+
pillow = "PIL"
|
| 23 |
+
|
| 24 |
+
[build-system]
|
| 25 |
+
requires = ["setuptools", "wheel", "setuptools-scm"]
|
| 26 |
+
build-backend = "setuptools.build_meta"
|
| 27 |
+
|
| 28 |
+
[tool.setuptools.packages.find]
|
| 29 |
+
namespaces = true
|
| 30 |
+
where = ["src"]
|
pyproject.toml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This is the workspace configuration. Each individual package has its own pyproject.toml.
|
| 2 |
+
[project]
|
| 3 |
+
name = "lynxkite-workspace"
|
| 4 |
+
version = "0.1.0"
|
| 5 |
+
requires-python = ">=3.12"
|
| 6 |
+
dependencies = ["lynxkite", "lynxkite-core", "lynxkite-graph-analytics[gpu]", "lynxkite-pillow-example"]
|
| 7 |
+
classifiers = ["Private :: Do Not Upload"]
|
| 8 |
+
|
| 9 |
+
[tool.uv.sources]
|
| 10 |
+
lynxkite = { workspace = true }
|
| 11 |
+
lynxkite-core = { workspace = true }
|
| 12 |
+
lynxkite-graph-analytics = { workspace = true }
|
| 13 |
+
lynxkite-pillow-example = { workspace = true }
|
| 14 |
+
|
| 15 |
+
[tool.uv.workspace]
|
| 16 |
+
members = [
|
| 17 |
+
"lynxkite-app",
|
| 18 |
+
"lynxkite-core",
|
| 19 |
+
"lynxkite-graph-analytics",
|
| 20 |
+
"lynxkite-pillow-example",
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
[dependency-groups]
|
| 24 |
+
dev = [
|
| 25 |
+
"deptry>=0.23.0",
|
| 26 |
+
"mkdocs-material>=9.6.15",
|
| 27 |
+
"mkdocstrings[python]>=0.29.1",
|
| 28 |
+
"pytest>=8.4.1",
|
| 29 |
+
"pytest-asyncio>=1.1.0",
|
| 30 |
+
]
|
| 31 |
+
|
| 32 |
+
[tool.pytest.ini_options]
|
| 33 |
+
asyncio_mode = "auto"
|
test.sh
DELETED
|
@@ -1,6 +0,0 @@
|
|
| 1 |
-
#!/bin/bash -xue
|
| 2 |
-
|
| 3 |
-
cd "$(dirname $0)"
|
| 4 |
-
pytest --asyncio-mode=auto
|
| 5 |
-
cd lynxkite-app/web
|
| 6 |
-
npm run test
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|