darabos commited on
Commit
20d727f
·
1 Parent(s): 11c6a00

Set up a "workspace" package, fix up dependencies, set up Deptry.

Browse files
.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 pip install -e 'lynxkite-core/[dev]' -e 'lynxkite-app/[dev]' -e 'lynxkite-graph-analytics/[dev]' -e lynxkite-pillow-example/
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
- ./test.sh
 
 
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
- "griffe>=1.7.3",
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
- [project.optional-dependencies]
21
  dev = [
22
  "pydantic-to-typescript>=2.0.0",
23
- "pytest>=8.3.4",
24
  ]
25
 
26
  [tool.uv.sources]
27
- lynxkite-core = { path = "../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
- [project.optional-dependencies]
15
- dev = [
16
- "pytest",
17
- ]
 
 
 
18
 
19
- [tool.pytest.ini_options]
20
- asyncio_mode = "auto"
 
 
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
- if cache:
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
- if cache is not None:
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 isinstance(output, pd.DataFrame):
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 = mem.cache(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 = { path = "../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 = { path = "../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