Skip to content
Open
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
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ git clone https://github.com/EBB2675/schema-uml.git
cd schema-uml
```

### 2) Environment (Python 3.11)
### 2) Environment (Python 3.11, managed by [uv](https://docs.astral.sh/uv/))
```bash
conda create -n schema-uml python=3.11 -y
conda activate schema-uml
pip install -r api/requirements.txt
pip install --user uv # or: pipx install uv, brew install uv
uv venv # creates .venv in the repo
source .venv/bin/activate
uv sync # installs backend + dev deps from pyproject.toml
```

### 3) Point to your schema repo
Expand All @@ -67,6 +68,10 @@ export SCHEMA_UML_REPO=<path-or-URL-to-your-schema-repo>
```
Make it persistent by adding the export to `~/.bashrc` or `~/.zshrc`.

The backend will automatically prepend that repo (and `src/` if present) to `PYTHONPATH`,
so packages like `nomad_simulations` resolve without extra setup once the environment
variable points to your clone.

### 4) Run everything with one command
```bash
./dev.sh
Expand All @@ -79,7 +84,7 @@ What it does:
- Ensures `web/node_modules` exists (runs `npm install` on first launch).
- Starts the Vite frontend on **5173**.
- Stops both together on **Ctrl+C** (no manual job control needed).
- Exits early with a helpful message if `uvicorn` or `npm` are missing (activate your virtualenv first).
- Exits early with a helpful message if `uvicorn` or `npm` are missing (activate your `.venv` first).

Stop both with **Ctrl+C**. Override ports via `API_PORT` / `WEB_PORT` env vars.

Expand Down Expand Up @@ -160,7 +165,7 @@ Legend:
- Clear cache: `rm -rf web/node_modules web/node_modules/.vite && npm i`.
- **Pydantic import error (`model_validator`)**
- The backend relies on **Pydantic v2** (`pydantic>=2,<3`).
- If you see `ImportError: cannot import name 'model_validator'`, an older global install may be shadowing your environment; reinstall requirements inside a clean virtualenv/conda env to pick up v2.
- If you see `ImportError: cannot import name 'model_validator'`, an older global install may be shadowing your environment; recreate the env (`rm -rf .venv && uv venv && uv sync`) to pick up v2.

---

Expand Down
16 changes: 15 additions & 1 deletion REPO_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ export SCHEMA_UML_BASE_PACKAGE=my_schema_root
export SCHEMA_UML_PACKAGE=my_schema_root.module
~~~

The backend prepends that repository (and `src/` when present) to `PYTHONPATH`, so
packages like `nomad_simulations` import cleanly without manual path tweaks as long as
one of the env vars above points to your clone.

**Backend environment (Python 3.11, managed by [uv](https://docs.astral.sh/uv/))**

~~~bash
pip install --user uv # or: pipx install uv, brew install uv
uv venv # creates .venv in the repo
source .venv/bin/activate
uv sync # installs backend + dev deps from pyproject.toml
~~~

**Unified dev command:** `./dev.sh` starts the FastAPI backend (**5179**) and Vite frontend (**5173**), checks for `uvicorn`/`npm`, validates **SCHEMA_UML_REPO / NOMAD_SIM_REPO / GIT_REPO_DIR** points to a local git repo (a subdirectory of a clone is fine), installs frontend deps on first run, and stops both on **Ctrl+C**. Override ports via `API_PORT` / `WEB_PORT`.

**UX highlights:**
Expand All @@ -56,14 +69,15 @@ export SCHEMA_UML_PACKAGE=my_schema_root.module

~~~text
schema-uml/
├─ pyproject.toml # Backend deps + dev deps (uv-managed)
├─ api/ # FastAPI backend
│ ├─ main.py # App entry, CORS, /roots, /schema, /overview, /usage, /schema/custom-quantity
│ ├─ routes_git.py # /git/branches, /git/packages, /graph, /graph/diff
│ ├─ graph_runner.py # Runs extractor in a worktree subprocess
│ ├─ git_utils.py # Bare mirror & worktree management
│ ├─ diff.py # Graph comparison logic
│ ├─ _data/ # Auto-generated bare mirror & worktrees (gitignored)
│ └─ requirements.txt
│ └─ requirements.txt # Legacy pin list (pyproject.toml is the source of truth)
├─ extractor/
│ ├─ graph_builder.py # build_graph(package, **opts); embeds docstrings
Expand Down
33 changes: 31 additions & 2 deletions api/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import List, Optional
import sys

from fastapi import FastAPI, Query, HTTPException
from fastapi.responses import ORJSONResponse
Expand Down Expand Up @@ -58,6 +59,7 @@ def root():
def roots(package: str = Query(...)):
"""List available section classes for a given package."""
try:
_ensure_repo_on_path(package)
return {"package": package, "sections": sorted(list_sections(package))}
except Exception as e:
raise HTTPException(status_code=400, detail=f"{type(e).__name__}: {e}")
Expand All @@ -68,9 +70,10 @@ def schema(
root: str | None = Query(None),
include_quantities: bool = Query(True),
include_subsections: bool = Query(True),
allow_cross_module: bool = Query(True),
base_namespace: str | None = Query(None),
allow_cross_module: bool = Query(True),
base_namespace: str | None = Query(None),
):
_ensure_repo_on_path(package, base_namespace)
data = build_graph(
package=package,
root=root,
Expand Down Expand Up @@ -100,6 +103,31 @@ def _repo_root(base_package: str | None = None) -> Path:
)
return repo_path


def _base_namespace_from(package: str, base_namespace: str | None = None) -> str:
"""Best-effort mapping from a package name to its base namespace."""

if base_namespace:
return base_namespace

parts = [chunk for chunk in package.split(".") if chunk]
if len(parts) >= 2:
return ".".join(parts[:2])
if parts:
return parts[0]
return "nomad_simulations"


def _ensure_repo_on_path(package: str, base_namespace: str | None = None) -> None:
"""Add the schema repo (and src/) to sys.path for dynamic imports."""

base_pkg = _base_namespace_from(package, base_namespace)
repo = _repo_root(base_pkg)
for candidate in (repo, repo / "src"):
candidate_str = str(candidate)
if candidate_str not in sys.path:
sys.path.insert(0, candidate_str)

def _run_git(repo: Path, *args: str) -> str:
cp = subprocess.run(["git", "-C", str(repo), *args], capture_output=True, text=True)
if cp.returncode != 0:
Expand Down Expand Up @@ -270,6 +298,7 @@ def add_custom_quantity(
base_namespace: str | None = Query(None),
):
try:
_ensure_repo_on_path(req.package, base_namespace)
graph = build_graph(
package=req.package,
root=root,
Expand Down
2 changes: 1 addition & 1 deletion api/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ def repo_for_base_namespace(base_package: str) -> str:
base = base_package.strip()
if base.startswith("nomad_measurements"):
return MEASURE_REPO
return SCHEMA_REPO
return SCHEMA_REPO
28 changes: 28 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[project]
name = "schema-uml"
version = "0.1.0"
description = "UML-style viewer for NOMAD-compatible schemas"
authors = [{ name = "Dr. Esma Birsen Boydaş" }]
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.110",
"uvicorn",
"pydantic>=2,<3",
"networkx",
"gitpython",
"orjson",
"httpx",
]

[dependency-groups]
dev = ["pytest>=7"]

[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
include = ["api*", "extractor*"]
exclude = ["web*", "assets*"]

[tool.uv]
Loading