feat(phase-0): bootstrap project with TDD structure (#1)
Browse files* feat(phase-0): bootstrap project with TDD structure
Phase 0 complete - repo bootstrap with 2025 Python best practices:
- pyproject.toml with uv + hatchling backend
- Tobias's datasets fork for BIDS/NIfTI support
- src/stroke_deepisles_demo/ package structure
- core/ module with config, types, exceptions stubs
- data/, inference/, ui/ module stubs
- tests/ with pytest smoke tests (TDD - written first!)
- ruff for linting + formatting
- mypy strict mode on src/ and tests/
- pre-commit hooks configured
All "done" criteria verified:
- uv sync β
- pytest (3 passed) β
- ruff check β
- ruff format --check β
- mypy src/ tests/ β
- pre-commit run --all-files β
- package import β
* fix: address CodeRabbit review - ironclad Gucci banger status
Fixes all nitpicks from CodeRabbit review:
1. Python version alignment:
- ruff target-version: py311 β py312
- mypy python_version: 3.11 β 3.12
- Now aligned with .python-version (3.12)
2. Author info updated:
- Removed placeholder "Your Name" β "stroke-deepisles-demo contributors"
3. Consolidated dev deps:
- Removed duplicate [tool.uv].dev-dependencies section
- Single source of truth: [dependency-groups].dev
4. Pre-commit versions aligned:
- ruff: v0.8.0 β v0.14.8
- mypy: v1.8.0 β v1.19.0
- pre-commit-hooks: v4.5.0 β v6.0.0
- pyproject.toml minimums updated to match
5. Pydantic v2 modern style:
- Replaced nested `class Config` with `model_config = SettingsConfigDict(...)`
6. TypedDict improved:
- Changed flair/ground_truth from `Path | None` (required key, optional value)
- To `NotRequired[Path]` (optional key entirely)
- Added comprehensive docstring explaining required vs optional keys
All checks pass: pytest β ruff β mypy β pre-commit β
- .gitignore +2 -2
- .pre-commit-config.yaml +25 -0
- .python-version +1 -0
- pyproject.toml +134 -0
- src/stroke_deepisles_demo/__init__.py +5 -0
- src/stroke_deepisles_demo/core/__init__.py +23 -0
- src/stroke_deepisles_demo/core/config.py +29 -0
- src/stroke_deepisles_demo/core/exceptions.py +23 -0
- src/stroke_deepisles_demo/core/types.py +37 -0
- src/stroke_deepisles_demo/data/__init__.py +1 -0
- src/stroke_deepisles_demo/inference/__init__.py +1 -0
- src/stroke_deepisles_demo/py.typed +0 -0
- src/stroke_deepisles_demo/ui/__init__.py +1 -0
- tests/__init__.py +1 -0
- tests/conftest.py +5 -0
- tests/test_package.py +29 -0
- uv.lock +0 -0
|
@@ -182,9 +182,9 @@ cython_debug/
|
|
| 182 |
.abstra/
|
| 183 |
|
| 184 |
# Visual Studio Code
|
| 185 |
-
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
| 186 |
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
| 187 |
-
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
| 188 |
# you could uncomment the following to ignore the entire vscode folder
|
| 189 |
# .vscode/
|
| 190 |
|
|
|
|
| 182 |
.abstra/
|
| 183 |
|
| 184 |
# Visual Studio Code
|
| 185 |
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
| 186 |
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
| 187 |
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
| 188 |
# you could uncomment the following to ignore the entire vscode folder
|
| 189 |
# .vscode/
|
| 190 |
|
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
repos:
|
| 2 |
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
| 3 |
+
rev: v0.14.8
|
| 4 |
+
hooks:
|
| 5 |
+
- id: ruff
|
| 6 |
+
args: [--fix]
|
| 7 |
+
- id: ruff-format
|
| 8 |
+
|
| 9 |
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
| 10 |
+
rev: v1.19.0
|
| 11 |
+
hooks:
|
| 12 |
+
- id: mypy
|
| 13 |
+
additional_dependencies:
|
| 14 |
+
- pydantic>=2.5.0
|
| 15 |
+
- pydantic-settings>=2.1.0
|
| 16 |
+
args: [--config-file=pyproject.toml]
|
| 17 |
+
|
| 18 |
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
| 19 |
+
rev: v6.0.0
|
| 20 |
+
hooks:
|
| 21 |
+
- id: trailing-whitespace
|
| 22 |
+
- id: end-of-file-fixer
|
| 23 |
+
- id: check-yaml
|
| 24 |
+
- id: check-added-large-files
|
| 25 |
+
args: [--maxkb=1000]
|
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.12
|
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "stroke-deepisles-demo"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Demo: HF datasets + DeepISLES stroke segmentation + Gradio visualization"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
license = { text = "MIT" }
|
| 7 |
+
requires-python = ">=3.11"
|
| 8 |
+
authors = [
|
| 9 |
+
{ name = "stroke-deepisles-demo contributors" }
|
| 10 |
+
]
|
| 11 |
+
classifiers = [
|
| 12 |
+
"Development Status :: 3 - Alpha",
|
| 13 |
+
"Intended Audience :: Science/Research",
|
| 14 |
+
"License :: OSI Approved :: MIT License",
|
| 15 |
+
"Programming Language :: Python :: 3.11",
|
| 16 |
+
"Programming Language :: Python :: 3.12",
|
| 17 |
+
"Topic :: Scientific/Engineering :: Medical Science Apps.",
|
| 18 |
+
]
|
| 19 |
+
keywords = ["stroke", "neuroimaging", "segmentation", "BIDS", "NIfTI", "deep-learning"]
|
| 20 |
+
|
| 21 |
+
dependencies = [
|
| 22 |
+
# Core - pinned to Tobias's fork for BIDS + NIfTI lazy loading
|
| 23 |
+
"datasets @ git+https://github.com/CloseChoice/datasets.git@feat/bids-loader-streaming-upload-fix",
|
| 24 |
+
"huggingface-hub>=0.25.0",
|
| 25 |
+
|
| 26 |
+
# NIfTI handling
|
| 27 |
+
"nibabel>=5.2.0",
|
| 28 |
+
"numpy>=1.26.0",
|
| 29 |
+
|
| 30 |
+
# Configuration
|
| 31 |
+
"pydantic>=2.5.0",
|
| 32 |
+
"pydantic-settings>=2.1.0",
|
| 33 |
+
|
| 34 |
+
# UI (Gradio 5.x)
|
| 35 |
+
"gradio>=5.0.0",
|
| 36 |
+
]
|
| 37 |
+
|
| 38 |
+
[dependency-groups]
|
| 39 |
+
dev = [
|
| 40 |
+
"pytest>=8.0.0",
|
| 41 |
+
"pytest-cov>=4.1.0",
|
| 42 |
+
"pytest-mock>=3.12.0",
|
| 43 |
+
"mypy>=1.19.0",
|
| 44 |
+
"ruff>=0.14.0",
|
| 45 |
+
"pre-commit>=3.6.0",
|
| 46 |
+
# Type stubs
|
| 47 |
+
"types-requests",
|
| 48 |
+
]
|
| 49 |
+
|
| 50 |
+
[build-system]
|
| 51 |
+
requires = ["hatchling"]
|
| 52 |
+
build-backend = "hatchling.build"
|
| 53 |
+
|
| 54 |
+
[tool.hatch.metadata]
|
| 55 |
+
allow-direct-references = true
|
| 56 |
+
|
| 57 |
+
[tool.hatch.build.targets.wheel]
|
| 58 |
+
packages = ["src/stroke_deepisles_demo"]
|
| 59 |
+
|
| 60 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 61 |
+
# Tool configurations
|
| 62 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 63 |
+
|
| 64 |
+
[tool.ruff]
|
| 65 |
+
target-version = "py312"
|
| 66 |
+
line-length = 100
|
| 67 |
+
src = ["src", "tests"]
|
| 68 |
+
|
| 69 |
+
[tool.ruff.lint]
|
| 70 |
+
select = [
|
| 71 |
+
"E", # pycodestyle errors
|
| 72 |
+
"W", # pycodestyle warnings
|
| 73 |
+
"F", # pyflakes
|
| 74 |
+
"I", # isort
|
| 75 |
+
"B", # flake8-bugbear
|
| 76 |
+
"C4", # flake8-comprehensions
|
| 77 |
+
"UP", # pyupgrade
|
| 78 |
+
"ARG", # flake8-unused-arguments
|
| 79 |
+
"SIM", # flake8-simplify
|
| 80 |
+
"TCH", # flake8-type-checking
|
| 81 |
+
"PTH", # flake8-use-pathlib
|
| 82 |
+
"RUF", # ruff-specific
|
| 83 |
+
]
|
| 84 |
+
ignore = [
|
| 85 |
+
"E501", # line too long (handled by formatter)
|
| 86 |
+
]
|
| 87 |
+
|
| 88 |
+
[tool.ruff.lint.isort]
|
| 89 |
+
known-first-party = ["stroke_deepisles_demo"]
|
| 90 |
+
|
| 91 |
+
[tool.mypy]
|
| 92 |
+
python_version = "3.12"
|
| 93 |
+
strict = true
|
| 94 |
+
warn_return_any = true
|
| 95 |
+
warn_unused_ignores = true
|
| 96 |
+
disallow_untyped_defs = true
|
| 97 |
+
plugins = ["pydantic.mypy"]
|
| 98 |
+
|
| 99 |
+
[[tool.mypy.overrides]]
|
| 100 |
+
module = [
|
| 101 |
+
"nibabel.*",
|
| 102 |
+
"gradio.*",
|
| 103 |
+
"datasets.*",
|
| 104 |
+
"niivue.*",
|
| 105 |
+
]
|
| 106 |
+
ignore_missing_imports = true
|
| 107 |
+
|
| 108 |
+
[tool.pytest.ini_options]
|
| 109 |
+
testpaths = ["tests"]
|
| 110 |
+
python_files = ["test_*.py"]
|
| 111 |
+
python_functions = ["test_*"]
|
| 112 |
+
addopts = [
|
| 113 |
+
"-v",
|
| 114 |
+
"--tb=short",
|
| 115 |
+
"--strict-markers",
|
| 116 |
+
]
|
| 117 |
+
markers = [
|
| 118 |
+
"integration: marks tests requiring external resources (Docker, network)",
|
| 119 |
+
"slow: marks tests that take >10s to run",
|
| 120 |
+
]
|
| 121 |
+
filterwarnings = [
|
| 122 |
+
"ignore::DeprecationWarning",
|
| 123 |
+
]
|
| 124 |
+
|
| 125 |
+
[tool.coverage.run]
|
| 126 |
+
source = ["src/stroke_deepisles_demo"]
|
| 127 |
+
branch = true
|
| 128 |
+
|
| 129 |
+
[tool.coverage.report]
|
| 130 |
+
exclude_lines = [
|
| 131 |
+
"pragma: no cover",
|
| 132 |
+
"if TYPE_CHECKING:",
|
| 133 |
+
"raise NotImplementedError",
|
| 134 |
+
]
|
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""stroke-deepisles-demo: HF datasets + DeepISLES + Gradio visualization."""
|
| 2 |
+
|
| 3 |
+
__version__ = "0.1.0"
|
| 4 |
+
|
| 5 |
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Core utilities for stroke-deepisles-demo."""
|
| 2 |
+
|
| 3 |
+
from stroke_deepisles_demo.core.config import Settings, settings
|
| 4 |
+
from stroke_deepisles_demo.core.exceptions import (
|
| 5 |
+
DataLoadError,
|
| 6 |
+
DeepISLESError,
|
| 7 |
+
DockerNotAvailableError,
|
| 8 |
+
MissingInputError,
|
| 9 |
+
StrokeDemoError,
|
| 10 |
+
)
|
| 11 |
+
from stroke_deepisles_demo.core.types import CaseFiles, InferenceResult
|
| 12 |
+
|
| 13 |
+
__all__ = [
|
| 14 |
+
"CaseFiles",
|
| 15 |
+
"DataLoadError",
|
| 16 |
+
"DeepISLESError",
|
| 17 |
+
"DockerNotAvailableError",
|
| 18 |
+
"InferenceResult",
|
| 19 |
+
"MissingInputError",
|
| 20 |
+
"Settings",
|
| 21 |
+
"StrokeDemoError",
|
| 22 |
+
"settings",
|
| 23 |
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Application configuration using pydantic-settings."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class Settings(BaseSettings):
|
| 9 |
+
"""Application settings loaded from environment variables."""
|
| 10 |
+
|
| 11 |
+
model_config = SettingsConfigDict(
|
| 12 |
+
env_prefix="STROKE_DEMO_",
|
| 13 |
+
env_file=".env",
|
| 14 |
+
env_file_encoding="utf-8",
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
# HuggingFace
|
| 18 |
+
hf_dataset_id: str = "YongchengYAO/ISLES24-MR-Lite"
|
| 19 |
+
hf_cache_dir: str | None = None
|
| 20 |
+
|
| 21 |
+
# DeepISLES
|
| 22 |
+
deepisles_docker_image: str = "isleschallenge/deepisles"
|
| 23 |
+
deepisles_fast_mode: bool = True
|
| 24 |
+
|
| 25 |
+
# Paths
|
| 26 |
+
temp_dir: str | None = None
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
settings = Settings()
|
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Custom exceptions for stroke-deepisles-demo."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class StrokeDemoError(Exception):
|
| 7 |
+
"""Base exception for stroke-deepisles-demo."""
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class DataLoadError(StrokeDemoError):
|
| 11 |
+
"""Failed to load data from HuggingFace Hub."""
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class DockerNotAvailableError(StrokeDemoError):
|
| 15 |
+
"""Docker is not installed or not running."""
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class DeepISLESError(StrokeDemoError):
|
| 19 |
+
"""DeepISLES inference failed."""
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class MissingInputError(StrokeDemoError):
|
| 23 |
+
"""Required input files are missing."""
|
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared type definitions."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import TYPE_CHECKING, NotRequired, TypedDict
|
| 7 |
+
|
| 8 |
+
if TYPE_CHECKING:
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class CaseFiles(TypedDict):
|
| 13 |
+
"""Paths to NIfTI files for a single case.
|
| 14 |
+
|
| 15 |
+
Required keys:
|
| 16 |
+
dwi: Path to DWI NIfTI file
|
| 17 |
+
adc: Path to ADC NIfTI file
|
| 18 |
+
|
| 19 |
+
Optional keys (may be absent):
|
| 20 |
+
flair: Path to FLAIR NIfTI file (not all cases have FLAIR)
|
| 21 |
+
ground_truth: Path to ground truth mask (not available during inference)
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
dwi: Path
|
| 25 |
+
adc: Path
|
| 26 |
+
flair: NotRequired[Path]
|
| 27 |
+
ground_truth: NotRequired[Path]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@dataclass(frozen=True)
|
| 31 |
+
class InferenceResult:
|
| 32 |
+
"""Result of running DeepISLES on a case."""
|
| 33 |
+
|
| 34 |
+
case_id: str
|
| 35 |
+
input_files: CaseFiles
|
| 36 |
+
prediction_mask: Path
|
| 37 |
+
elapsed_seconds: float
|
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Data loading module for stroke-deepisles-demo."""
|
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""DeepISLES inference module for stroke-deepisles-demo."""
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Gradio UI module for stroke-deepisles-demo."""
|
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Tests for stroke-deepisles-demo."""
|
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared pytest fixtures for stroke-deepisles-demo tests."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
# No fixtures needed for Phase 0 - pure import tests
|
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Smoke tests for package structure."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def test_package_imports() -> None:
|
| 7 |
+
"""Verify the package can be imported."""
|
| 8 |
+
import stroke_deepisles_demo
|
| 9 |
+
|
| 10 |
+
assert stroke_deepisles_demo.__version__ == "0.1.0"
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def test_core_modules_import() -> None:
|
| 14 |
+
"""Verify core modules can be imported without side effects."""
|
| 15 |
+
from stroke_deepisles_demo.core import config, exceptions, types
|
| 16 |
+
|
| 17 |
+
assert config.settings is not None
|
| 18 |
+
assert types.CaseFiles is not None
|
| 19 |
+
assert exceptions.StrokeDemoError is not None
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def test_subpackages_exist() -> None:
|
| 23 |
+
"""Verify subpackage structure exists."""
|
| 24 |
+
from stroke_deepisles_demo import data, inference, ui
|
| 25 |
+
|
| 26 |
+
# These are stubs, just verify they exist
|
| 27 |
+
assert data is not None
|
| 28 |
+
assert inference is not None
|
| 29 |
+
assert ui is not None
|
|
The diff for this file is too large to render.
See raw diff
|
|
|