-
Notifications
You must be signed in to change notification settings - Fork 848
π¦ feat(inspect): Add docker build #3136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/geti-inspect
Are you sure you want to change the base?
Changes from 97 commits
5bdd704
918ba83
82dd6b0
1588d72
22f8f8f
978a895
cbb0a46
644f594
072ef38
0614756
e07a955
61d2481
e04ef66
3489422
4e160a1
7437855
590ece3
d9a613d
6d51c68
1baa2eb
0a2af70
8f1ef19
f0339cd
237731d
2a4f257
7653915
f0a7643
df8eb42
9fc19c1
3a446de
a2df1cb
dc08cf0
b84f44c
d63f53d
fed89a3
390f97f
f6ae55f
3da55dd
42c5541
4339b78
931af9c
cdeeaff
6ddb323
be6fe07
31c3d2f
701aca5
6bf1e55
71ca2c1
1d14bf1
dace0f0
cf91762
9096eb7
87a1efe
19a3461
64fe8ea
32f527a
1d7b9c7
1a08bee
22aa6df
5008712
0500abf
48365ba
ab12f0e
12fa1a4
e8476dc
0202e60
80e459a
bdad999
0b08527
8adc3a1
0773110
9990aca
5cb2bc6
a9c5f70
09d9f27
40c5e5a
4d09a21
0a232b8
c7ebce5
ef63ed2
f0ef85b
c0d3c78
438a499
70f05fa
39209fc
5cf0ad6
83a7c16
85d4860
54501fb
5d3a63c
bdac68a
3af24ca
0e450d1
86e137a
0740a18
be206fe
9ae1a10
bada298
419b839
f1e4943
4a879de
afbaae0
a1a0ed9
e0cdf7f
1cf42f6
c5a4f05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| # Git | ||
| .git | ||
| .gitignore | ||
| .gitattributes | ||
|
|
||
| # Python | ||
| __pycache__/ | ||
| *.py[cod] | ||
| *$py.class | ||
| *.so | ||
| .Python | ||
| build/ | ||
| develop-eggs/ | ||
| dist/ | ||
| downloads/ | ||
| eggs/ | ||
| .eggs/ | ||
| lib/ | ||
| lib64/ | ||
| parts/ | ||
| sdist/ | ||
| var/ | ||
| wheels/ | ||
| *.egg-info/ | ||
| .installed.cfg | ||
| *.egg | ||
| .pytest_cache/ | ||
| .coverage | ||
| htmlcov/ | ||
| .tox/ | ||
| .nox/ | ||
| .hypothesis/ | ||
|
|
||
| # Virtual environments | ||
| venv/ | ||
| env/ | ||
| ENV/ | ||
| .venv | ||
|
|
||
| # IDEs | ||
| .vscode/ | ||
| .idea/ | ||
| *.swp | ||
| *.swo | ||
| *~ | ||
| .DS_Store | ||
|
|
||
| # Node | ||
| node_modules/ | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
|
|
||
| # Application data (will be mounted as volumes) | ||
| application/backend/data/** | ||
| application/backend/logs/** | ||
| application/backend/openvino_cache/** | ||
| application/backend/tests/** | ||
| application/backend/**/__pycache__/** | ||
| application/backend/.*_cache/** | ||
| application/backend/.tmp/** | ||
| application/backend/.venv/** | ||
| application/ui/dist/ | ||
| application/ui/build/ | ||
| application/ui/node_modules/ | ||
| data/ | ||
|
|
||
| # Documentation and examples | ||
| docs/ | ||
| examples/ | ||
| *.md | ||
| !README.md | ||
|
|
||
| # CI/CD | ||
| .github/ | ||
| .gitlab-ci.yml | ||
| .travis.yml | ||
|
|
||
| # Testing | ||
| tests/ | ||
| *.test.js | ||
| *.spec.js | ||
| coverage/ | ||
|
|
||
| # Temporary files | ||
| tmp/ | ||
| temp/ | ||
| *.tmp | ||
| *.log | ||
|
|
||
| # Datasets and model artifacts | ||
| datasets/ | ||
| pre_trained/ | ||
| results/ | ||
| openvino_cache/ | ||
|
|
||
| # Experiment tracking and logs | ||
| wandb/ | ||
| lightning_logs/ | ||
| mlruns/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -176,3 +176,7 @@ docs/source/_build/ | |
| wandb/ | ||
| lightning_logs/ | ||
| mlruns | ||
|
|
||
| # application data | ||
| data/ | ||
| logs/ | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,20 @@ | ||||||||||||||||||||||
| # Copyright (C) 2025 Intel Corporation | ||||||||||||||||||||||
| # SPDX-License-Identifier: Apache-2.0 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from fastapi import APIRouter, HTTPException | ||||||||||||||||||||||
| from fastapi.responses import FileResponse | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from settings import get_settings | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| settings = get_settings() | ||||||||||||||||||||||
| webui_router = APIRouter(tags=["Webui"]) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @webui_router.get("/", include_in_schema=False) | ||||||||||||||||||||||
ashwinvaidya17 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
| async def get_webui(full_path: str = "") -> FileResponse: # noqa: ARG001 | ||||||||||||||||||||||
|
||||||||||||||||||||||
| async def get_webui(full_path: str = "") -> FileResponse: # noqa: ARG001 | |
| async def get_webui() -> FileResponse: |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If settings.static_files_dir is None or empty, file_path will not be defined, causing an UnboundLocalError on line 20. The condition should ensure file_path is always defined before the return statement, or raise an appropriate error when static_files_dir is not configured.
| if settings.static_files_dir and not (file_path := Path(settings.static_files_dir) / "index.html").exists(): | |
| if not settings.static_files_dir: | |
| raise HTTPException(status_code=500, detail="Static files directory is not configured") | |
| file_path = Path(settings.static_files_dir) / "index.html" | |
| if not file_path.exists(): |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If static_files_dir is None or not set, file_path will be undefined when reaching the return statement, causing a NameError. The logic should handle the case where static_files_dir is None by raising an HTTPException before attempting to return FileResponse.
| if settings.static_files_dir and not (file_path := Path(settings.static_files_dir) / "index.html").exists(): | |
| if not settings.static_files_dir: | |
| raise HTTPException(status_code=404, detail="Static files directory not set") | |
| file_path = Path(settings.static_files_dir) / "index.html" | |
| if not file_path.exists(): |
Copilot
AI
Dec 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable file_path is only assigned when the condition is True, but it's used unconditionally in the return statement. If settings.static_files_dir is None or empty, file_path will be undefined, causing a NameError. Initialize file_path before the conditional or handle the None case explicitly.
| if settings.static_files_dir and not (file_path := Path(settings.static_files_dir) / "index.html").exists(): | |
| raise HTTPException(status_code=404, detail="File not found") | |
| file_path = None | |
| if settings.static_files_dir: | |
| file_path = Path(settings.static_files_dir) / "index.html" | |
| if not file_path.exists(): | |
| raise HTTPException(status_code=404, detail="File not found") | |
| else: | |
| raise HTTPException(status_code=404, detail="Static files directory not configured") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,12 @@ | |
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import os | ||
| from pathlib import Path | ||
|
|
||
| import uvicorn | ||
| from fastapi import FastAPI | ||
| from fastapi.middleware.cors import CORSMiddleware | ||
| from fastapi.staticfiles import StaticFiles | ||
|
|
||
| from api.endpoints.active_pipeline_endpoints import router as active_pipeline_router | ||
| from api.endpoints.capture_endpoints import router as capture_router | ||
|
|
@@ -20,6 +22,7 @@ | |
| from api.endpoints.source_endpoints import router as source_router | ||
| from api.endpoints.trainable_models_endpoints import router as trainable_model_router | ||
| from api.endpoints.webrtc import router as webrtc_router | ||
| from api.endpoints.webui_endpoints import webui_router | ||
| from core.lifecycle import lifespan | ||
| from settings import get_settings | ||
|
|
||
|
|
@@ -34,15 +37,11 @@ | |
|
|
||
| _ = exception_handlers # to avoid import being removed by linters | ||
|
|
||
| # TODO: check if middleware is required | ||
| # Enable CORS for local test UI | ||
| app.add_middleware( | ||
| CORSMiddleware, | ||
| allow_origins=[ | ||
| "http://localhost:3000", | ||
| "http://localhost:9000", | ||
| "http://127.0.0.1:9000", | ||
| ], | ||
| allow_origins=["*"], | ||
|
||
| allow_credentials=True, | ||
| allow_methods=["*"], | ||
| allow_headers=["*"], | ||
|
|
@@ -61,8 +60,24 @@ | |
| app.include_router(capture_router) | ||
| app.include_router(snapshot_router) | ||
|
|
||
| settings = get_settings() | ||
|
|
||
| if __name__ == "__main__": | ||
| settings = get_settings() | ||
| # In docker deployment, the UI is built and served statically | ||
| if ( | ||
| settings.static_files_dir | ||
| and Path(settings.static_files_dir).is_dir() | ||
| and (Path(settings.static_files_dir) / "index.html").exists() | ||
| ): | ||
| static_dir = Path(settings.static_files_dir) | ||
| app.mount("/static", StaticFiles(directory=static_dir / "static"), name="static") | ||
| app.include_router(webui_router) | ||
|
|
||
|
|
||
| def main() -> None: | ||
| """Main function to run the Geti Inspect server""" | ||
| uvicorn_port = int(os.environ.get("HTTP_SERVER_PORT", settings.port)) | ||
| uvicorn.run("main:app", loop="uvloop", host=settings.host, port=uvicorn_port, log_config=None) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,9 @@ | |
| from pydantic import Field | ||
| from pydantic_settings import BaseSettings, SettingsConfigDict | ||
|
|
||
| # Get the directory where this settings module is located | ||
| _MODULE_DIR = Path(__file__).parent | ||
|
|
||
|
|
||
| class Settings(BaseSettings): | ||
| """Application settings with environment variable support""" | ||
|
|
@@ -26,6 +29,7 @@ class Settings(BaseSettings): | |
| environment: Literal["dev", "prod"] = "dev" | ||
| data_dir: Path = Field(default=Path("data"), alias="DATA_DIR") | ||
| log_dir: Path = Field(default=Path("logs"), alias="LOG_DIR") | ||
| static_files_dir: str | None = Field(default=None, alias="STATIC_FILES_DIR") | ||
|
|
||
| # Server | ||
| host: str = Field(default="0.0.0.0", alias="HOST") # noqa: S104 | ||
|
|
@@ -36,16 +40,16 @@ class Settings(BaseSettings): | |
| db_echo: bool = Field(default=False, alias="DB_ECHO") | ||
|
|
||
| # Alembic | ||
| alembic_config_path: str = "src/alembic.ini" | ||
| alembic_script_location: str = "src/alembic" | ||
| alembic_config_path: str = str(_MODULE_DIR / "alembic.ini") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be adjusted further when we introduce Pyinstaller |
||
| alembic_script_location: str = str(_MODULE_DIR / "alembic") | ||
|
|
||
| # Proxy settings | ||
| no_proxy: str = Field(default="localhost,127.0.0.1,::1", alias="no_proxy") | ||
|
|
||
| @property | ||
| def database_url(self) -> str: | ||
| """Get database URL""" | ||
| return f"sqlite+aiosqlite:///./{self.data_dir / self.database_file}?journal_mode=WAL" | ||
| return f"sqlite+aiosqlite:///{self.data_dir / self.database_file}?journal_mode=WAL" | ||
|
||
|
|
||
| @property | ||
| def sync_database_url(self) -> str: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would move this file into the
applicationfolder, since I think we shouldn't expect users to use docker for the library?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the context is the root of anomalib, it needs .dockerignore in the same folder