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
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
- Separate frontend app lives in `ui/`
- Legacy code lives in `legacy/`; avoid changing it unless the task explicitly targets legacy behavior
## Setup
- Install backend dependencies with `poetry install`
- Run the backend with `poetry run uvicorn geneweaver.api.main:app --reload`
- Install backend dependencies with `uv sync --all-packages --all-extras --all-groups`
- Run the backend with `uv run uvicorn geneweaver.api.main:app --reload`
- Install frontend dependencies with `cd ui && npm install`
- Run the frontend with `cd ui && npm run dev`
## Validation
- Lint backend changes with `ruff src tests --fix` and `black src tests`
- Lint backend changes with `ruff check src tests --fix` and `ruff format src tests`
- Run backend tests with `pytest tests --cov=geneweaver.api --cov-report term --cov-report html`
- For UI tasks, run the smallest relevant test or build command from `ui/`
- Prefer the smallest relevant test target first, then broaden only as needed
Expand Down
1 change: 0 additions & 1 deletion packages/client/tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
SourceType,
StrainResult,
)
from geneweaver.testing.fixtures import * # noqa: F403
from numpy.random import Generator
from pandas import DataFrame
from requests.exceptions import HTTPError
Expand Down
1 change: 0 additions & 1 deletion packages/core/tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import pytest
from geneweaver.core.config_class import CoreSettings
from geneweaver.testing.fixtures import * # noqa: F403


@pytest.fixture(scope="session")
Expand Down
6 changes: 0 additions & 6 deletions packages/db/tests/conftest.py

This file was deleted.

10 changes: 0 additions & 10 deletions packages/tools/tests/unit/conftest.py

This file was deleted.

11 changes: 0 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ Repository = "https://github.com/TheJacksonLaboratory/geneweaver"
dev = [
"pytest-asyncio>=0.21.0",
"ruff==0.15.11",
# Shared pytest fixtures imported by packages/*/tests (geneweaver.testing.fixtures).
"geneweaver-testing>=0.1.2",
]
# Documentation site (mkdocs.yml + docs/). Previously lived in the standalone
# TheJacksonLaboratory/geneweaver-docs repo, which this replaces.
Expand All @@ -46,15 +44,6 @@ docs = [
"mkdocs-jupyter>=0.24.2",
]

[tool.uv]
# geneweaver-testing pins ruff<0.3 for its own lint command, which is unsatisfiable
# against this project's ruff==0.15.11. We only use its pytest fixtures, so override the
# ruff pin. (Its pytest<9 / pytest-cov<5 caps are left to resolve naturally — pytest 9
# requires Python 3.10+ while this project supports 3.9, so we must not force pytest>=9.)
override-dependencies = [
"ruff==0.15.11",
]

[tool.uv.workspace]
members = [
"packages/*",
Expand Down
77 changes: 77 additions & 0 deletions tests/test_namespace_integrity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""`geneweaver` must stay a PEP 420 namespace package.

Five first-party distributions install into `geneweaver/`: the root `geneweaver-api`,
plus `geneweaver-core`, `geneweaver-db`, `geneweaver-client` and `geneweaver-tools`
from `packages/`. They can only coexist because `geneweaver` is a namespace package
rather than a regular one.

Adding an `__init__.py` to any single distribution's `src/geneweaver/` turns that
portion into a regular package and shadows the rest: the offending distribution keeps
importing fine, while the other four silently stop resolving. The symptom then appears
far from its cause -- usually as an `ImportError` in an unrelated package's tests -- so
it is worth asserting directly.

This replaces the equivalent check that used to come from the archived
`geneweaver-testing` package. Paths are derived from this file rather than from
pytest's `rootdir`, because CI runs the root suite from the repository root and each
package suite from inside that package's own directory, which moves `rootdir`.
"""

import importlib
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).resolve().parents[1]

#: Every distribution source root that contributes a portion to the namespace. Globbed
#: rather than hard-coded so a newly added package is covered without editing this file.
NAMESPACE_SOURCE_ROOTS = [
REPO_ROOT / "src",
*sorted((REPO_ROOT / "packages").glob("*/src")),
]

#: The subpackage each distribution provides under the `geneweaver` namespace.
FIRST_PARTY_SUBPACKAGES = ["api", "core", "db", "client", "tools"]


def _relative(path: Path) -> str:
"""Render a path relative to the repository root, for readable test ids."""
return str(path.relative_to(REPO_ROOT))


def test_namespace_source_roots_are_found() -> None:
"""Guard the discovery itself, so the checks below cannot vacuously pass."""
assert len(NAMESPACE_SOURCE_ROOTS) >= len(FIRST_PARTY_SUBPACKAGES), (
f"Expected at least {len(FIRST_PARTY_SUBPACKAGES)} distribution source roots, "
f"found {[_relative(p) for p in NAMESPACE_SOURCE_ROOTS]}"
)


@pytest.mark.parametrize("source_root", NAMESPACE_SOURCE_ROOTS, ids=_relative)
def test_geneweaver_portion_is_not_a_regular_package(source_root: Path) -> None:
"""No distribution may add an `__init__.py` to the shared namespace directory."""
init_file = source_root / "geneweaver" / "__init__.py"
assert not init_file.exists(), (
f"{_relative(init_file)} must not exist. It would make this distribution's "
"`geneweaver` portion a regular package and shadow every other distribution "
"sharing the namespace, breaking their imports."
)


def test_geneweaver_is_a_namespace_package() -> None:
"""The imported `geneweaver` package has no `__file__`, as namespace packages do."""
import geneweaver

assert getattr(geneweaver, "__file__", None) is None, (
"`geneweaver` resolved to a regular package "
f"({geneweaver.__file__}). Only one distribution's portion is now importable; "
"check for a stray `src/geneweaver/__init__.py`."
)


@pytest.mark.parametrize("subpackage", FIRST_PARTY_SUBPACKAGES)
def test_first_party_subpackage_is_importable(subpackage: str) -> None:
"""Each distribution resolves through the shared namespace."""
module = importlib.import_module(f"geneweaver.{subpackage}")
assert module is not None
Loading
Loading