lightning-pose-app 1.8.1a3__tar.gz → 1.8.1a5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. lightning_pose_app-1.8.1a5/PKG-INFO +23 -0
  2. lightning_pose_app-1.8.1a5/pyproject.toml +33 -0
  3. lightning_pose_app-1.8.1a5/src/litpose_app/config.py +22 -0
  4. lightning_pose_app-1.8.1a5/src/litpose_app/deps.py +77 -0
  5. lightning_pose_app-1.8.1a5/src/litpose_app/main.py +161 -0
  6. {lightning_pose_app-1.8.1a3 → lightning_pose_app-1.8.1a5}/src/litpose_app/ngdist/ng_app/3rdpartylicenses.txt +11 -11
  7. {lightning_pose_app-1.8.1a3 → lightning_pose_app-1.8.1a5}/src/litpose_app/ngdist/ng_app/index.html +3 -2
  8. lightning_pose_app-1.8.1a3/src/litpose_app/ngdist/ng_app/main-LJHMLKBL.js → lightning_pose_app-1.8.1a5/src/litpose_app/ngdist/ng_app/main-6XYUWDGZ.js +370 -203
  9. lightning_pose_app-1.8.1a5/src/litpose_app/ngdist/ng_app/main-6XYUWDGZ.js.map +1 -0
  10. lightning_pose_app-1.8.1a3/src/litpose_app/ngdist/ng_app/styles-4V6RXJMC.css → lightning_pose_app-1.8.1a5/src/litpose_app/ngdist/ng_app/styles-GMK322VW.css +32 -1
  11. lightning_pose_app-1.8.1a5/src/litpose_app/ngdist/ng_app/styles-GMK322VW.css.map +7 -0
  12. lightning_pose_app-1.8.1a3/src/litpose_app/run_ffprobe.py → lightning_pose_app-1.8.1a5/src/litpose_app/routes/ffprobe.py +164 -132
  13. lightning_pose_app-1.8.1a3/src/litpose_app/super_rglob.py → lightning_pose_app-1.8.1a5/src/litpose_app/routes/files.py +108 -48
  14. lightning_pose_app-1.8.1a5/src/litpose_app/routes/project.py +72 -0
  15. lightning_pose_app-1.8.1a5/src/litpose_app/routes/transcode.py +131 -0
  16. lightning_pose_app-1.8.1a5/src/litpose_app/tasks/__init__.py +0 -0
  17. lightning_pose_app-1.8.1a5/src/litpose_app/tasks/management.py +20 -0
  18. lightning_pose_app-1.8.1a5/src/litpose_app/tasks/transcode_fine.py +7 -0
  19. lightning_pose_app-1.8.1a5/src/litpose_app/transcode_fine.py +173 -0
  20. lightning_pose_app-1.8.1a3/PKG-INFO +0 -15
  21. lightning_pose_app-1.8.1a3/pyproject.toml +0 -23
  22. lightning_pose_app-1.8.1a3/src/litpose_app/main.py +0 -300
  23. lightning_pose_app-1.8.1a3/src/litpose_app/ngdist/ng_app/main-LJHMLKBL.js.map +0 -1
  24. lightning_pose_app-1.8.1a3/src/litpose_app/ngdist/ng_app/styles-4V6RXJMC.css.map +0 -7
  25. {lightning_pose_app-1.8.1a3 → lightning_pose_app-1.8.1a5}/src/litpose_app/__init__.py +0 -0
  26. /lightning_pose_app-1.8.1a3/src/litpose_app/ngdist/ng_app/app.component-UHVEDPZR.css.map → /lightning_pose_app-1.8.1a5/src/litpose_app/ngdist/ng_app/app.component-UAQUAGNZ.css.map +0 -0
  27. {lightning_pose_app-1.8.1a3 → lightning_pose_app-1.8.1a5}/src/litpose_app/ngdist/ng_app/error-dialog.component-HYLQSJEP.css.map +0 -0
  28. {lightning_pose_app-1.8.1a3 → lightning_pose_app-1.8.1a5}/src/litpose_app/ngdist/ng_app/favicon.ico +0 -0
  29. {lightning_pose_app-1.8.1a3 → lightning_pose_app-1.8.1a5}/src/litpose_app/ngdist/ng_app/prerendered-routes.json +0 -0
  30. /lightning_pose_app-1.8.1a3/src/litpose_app/ngdist/ng_app/project-settings.component-5IRK7U7U.css.map → /lightning_pose_app-1.8.1a5/src/litpose_app/ngdist/ng_app/project-settings.component-HKHIVUJR.css.map +0 -0
  31. {lightning_pose_app-1.8.1a3 → lightning_pose_app-1.8.1a5}/src/litpose_app/ngdist/ng_app/video-player-controls.component-C4JZHYJ2.css.map +0 -0
  32. /lightning_pose_app-1.8.1a3/src/litpose_app/ngdist/ng_app/video-tile.component-XSYKMARQ.css.map → /lightning_pose_app-1.8.1a5/src/litpose_app/ngdist/ng_app/video-tile.component-RDL4BSJ4.css.map +0 -0
  33. /lightning_pose_app-1.8.1a3/src/litpose_app/ngdist/ng_app/viewer-page.component-MRTIUFL2.css.map → /lightning_pose_app-1.8.1a5/src/litpose_app/ngdist/ng_app/viewer-page.component-KDHT6XH5.css.map +0 -0
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.3
2
+ Name: lightning-pose-app
3
+ Version: 1.8.1a5
4
+ Summary:
5
+ Requires-Python: >=3.10
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3.10
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Provides-Extra: dev
12
+ Requires-Dist: apscheduler (>=3.11.0,<3.12.0)
13
+ Requires-Dist: fastapi (>=0.115.0,<0.116.0)
14
+ Requires-Dist: honcho (>=2.0,<3.0)
15
+ Requires-Dist: httpx (>=0.28.0,<0.29.0) ; extra == "dev"
16
+ Requires-Dist: pytest (>=8.4.0,<8.5.0) ; extra == "dev"
17
+ Requires-Dist: pytest-asyncio (>=1.0.0,<1.1.0) ; extra == "dev"
18
+ Requires-Dist: pytest-mock (>=3.14.0,<3.15.0) ; extra == "dev"
19
+ Requires-Dist: reactivex (>=4.0.0,<4.1.0)
20
+ Requires-Dist: tomli (>=2.2.0,<2.3.0)
21
+ Requires-Dist: tomli_w (>=1.2.0,<1.3.0)
22
+ Requires-Dist: uvicorn[standard] (>=0.34.0,<0.35.0)
23
+ Requires-Dist: wcmatch (>=10.0,<11.0)
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["poetry-core>=1.0.0"]
3
+ build-backend = "poetry.core.masonry.api"
4
+
5
+ [project]
6
+ name = "lightning-pose-app"
7
+ version = "1.8.1a5"
8
+ requires-python = ">=3.10"
9
+ dependencies = [
10
+ "apscheduler~=3.11.0",
11
+ "fastapi~=0.115.0",
12
+ "honcho~=2.0",
13
+ "reactivex~=4.0.0",
14
+ "uvicorn[standard]~=0.34.0",
15
+ "wcmatch~=10.0",
16
+ "tomli~=2.2.0",
17
+ "tomli_w~=1.2.0",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ dev = [
22
+ "httpx~=0.28.0",
23
+ "pytest~=8.4.0",
24
+ "pytest-mock~=3.14.0",
25
+ "pytest-asyncio~=1.0.0"
26
+ ]
27
+
28
+ [tool.poetry]
29
+ packages = [
30
+ { include = "litpose_app", from = "src" }
31
+ ]
32
+ # We need this to override the .gitignore.
33
+ include = ["**/ngdist/**/*"]
@@ -0,0 +1,22 @@
1
+ """Routes should not access this directly, if they want to be able to
2
+ modify these in unit tests.
3
+ Instead, prefer to inject `config: deps.config into the route using FastAPI's dependency injection.
4
+ See https://fastapi.tiangolo.com/tutorial/dependencies/."""
5
+
6
+ from pathlib import Path
7
+
8
+ from pydantic import BaseModel
9
+
10
+
11
+ # Consider `pydantic_settings.BaseSettings` for potential future needs.
12
+ class Config(BaseModel):
13
+ PROJECT_INFO_TOML_PATH: Path = Path("~/.lightning_pose/project.toml").expanduser()
14
+
15
+ ## Video transcoding settings
16
+
17
+ # Directory where finely transcoded videos are stored
18
+ FINE_VIDEO_DIR: Path = Path("~/.lightning_pose/finevideos").expanduser()
19
+
20
+ # We'll automatically transcode videos with size under this limit.
21
+ # Larger ones will have to be manually triggered (design TBD).
22
+ AUTO_TRANSCODE_VIDEO_SIZE_LIMIT_MB: int = 30
@@ -0,0 +1,77 @@
1
+ """
2
+ Dependencies that can be injected into routes.
3
+ This has the benefit of making tests easier to write, as you can override dependencies.
4
+ See FastAPI Dependency Injection docs: https://fastapi.tiangolo.com/tutorial/dependencies/
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import math
11
+ import os
12
+ from typing import TYPE_CHECKING
13
+
14
+ from apscheduler.executors.debug import DebugExecutor
15
+ from apscheduler.schedulers.asyncio import AsyncIOScheduler
16
+ from apscheduler.executors.pool import ThreadPoolExecutor
17
+ from fastapi import Depends
18
+
19
+ from litpose_app.config import Config
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ def config() -> Config:
25
+ """Dependency that provides the app config object."""
26
+ from .main import app
27
+
28
+ if not hasattr(app.state, "config"):
29
+ app.state.config = Config()
30
+ return app.state.config
31
+
32
+
33
+ def scheduler() -> AsyncIOScheduler:
34
+ """Dependency that provides the app's APScheduler instance."""
35
+ from .main import app
36
+
37
+ if not hasattr(app.state, "scheduler"):
38
+ # ffmpeg parallelizes transcoding to the optimal degree, but
39
+ # that doesn't always saturate a machine with a lot of cores.
40
+ # i.e. on a 24 logical core machine (12 physical * 2 hyperthreads per core)
41
+ # 3 was the ideal number of max_workers. Let's just guesstimate that
42
+ # ffmpeg uses 10 cores? No scientific evidence, but ceil(24/10) => 3.
43
+ transcode_workers = math.ceil(os.cpu_count() / 10)
44
+ executors = {
45
+ "transcode_pool": ThreadPoolExecutor(max_workers=transcode_workers),
46
+ "debug": DebugExecutor(),
47
+ }
48
+ app.state.scheduler = AsyncIOScheduler(executors=executors)
49
+ return app.state.scheduler
50
+
51
+
52
+ if TYPE_CHECKING:
53
+ from .routes.project import ProjectInfo
54
+
55
+
56
+ def project_info(config: Config = Depends(config)) -> ProjectInfo:
57
+ import tomli
58
+ from .routes.project import ProjectInfo
59
+
60
+ from pydantic import ValidationError
61
+
62
+ try:
63
+ # Open the file in binary read mode, as recommended by tomli
64
+ with open(config.PROJECT_INFO_TOML_PATH, "rb") as f:
65
+ # Load the TOML data into a Python dictionary
66
+ toml_data = tomli.load(f)
67
+
68
+ # Unpack the dictionary into the Pydantic model
69
+ return ProjectInfo(**toml_data)
70
+ except FileNotFoundError:
71
+ return None
72
+ except tomli.TOMLDecodeError as e:
73
+ logger.error(f"Could not decode pyproject.toml. Invalid syntax: {e}")
74
+ raise
75
+ except ValidationError as e:
76
+ logger.error(f"pyproject.toml is invalid. {e}")
77
+ raise
@@ -0,0 +1,161 @@
1
+ import logging
2
+ import sys
3
+ from contextlib import asynccontextmanager
4
+ from pathlib import Path
5
+ from textwrap import dedent
6
+
7
+ import uvicorn
8
+ from fastapi import FastAPI, HTTPException, APIRouter, Request
9
+ from fastapi.responses import FileResponse
10
+ from starlette import status
11
+ from starlette.responses import Response
12
+ from starlette.staticfiles import StaticFiles
13
+
14
+ from . import deps
15
+ from .tasks.management import setup_active_task_registry
16
+
17
+ ## Setup logging
18
+ logging.basicConfig(
19
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
20
+ )
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ ## Configure additional things to happen on server startup and shutdown.
25
+ @asynccontextmanager
26
+ async def lifespan(app: FastAPI):
27
+ # Start apscheduler, which is responsible for executing background tasks
28
+ logger.info("Application startup: Initializing scheduler...")
29
+ scheduler = deps.scheduler()
30
+ app.state.scheduler = scheduler
31
+ scheduler.start()
32
+ setup_active_task_registry(app)
33
+
34
+ yield # Application is now ready to receive requests
35
+
36
+ logger.info("Application shutdown: Shutting down scheduler...")
37
+ if scheduler and scheduler.running:
38
+ scheduler.shutdown()
39
+ logger.info("Scheduler shut down.")
40
+ else:
41
+ logger.warning("Scheduler not found or not running during shutdown.")
42
+
43
+
44
+ app = FastAPI(lifespan=lifespan)
45
+
46
+ router = APIRouter()
47
+ from .routes import ffprobe, files, project, transcode
48
+
49
+ router.include_router(ffprobe.router)
50
+ router.include_router(files.router)
51
+ router.include_router(project.router)
52
+ router.include_router(transcode.router)
53
+ app.include_router(router)
54
+
55
+
56
+ @app.exception_handler(Exception)
57
+ async def debug_exception_handler(request: Request, exc: Exception):
58
+ """Puts error stack trace in response when any server exception occurs.
59
+
60
+ By default, FastAPI returns 500 "internal server error" on any Exception
61
+ that is not a subclass of HttpException. This is usually recommended in production apps.
62
+
63
+ In our app, it's more convenient to expose exception details to the user. The
64
+ security risk is minimal."""
65
+ import traceback
66
+
67
+ return Response(
68
+ status_code=500,
69
+ content="".join(
70
+ traceback.format_exception(type(exc), value=exc, tb=exc.__traceback__)
71
+ ),
72
+ headers={"Content-Type": "text/plain"},
73
+ )
74
+
75
+
76
+ """
77
+ All our methods are RPC style (http url corresponds to method name).
78
+ They should be POST requests, /rpc/<method_name>.
79
+ Request body is some object (pydantic model).
80
+ Response body is some object pydantic model.
81
+
82
+ The client expects all RPC methods to succeed. If any RPC doesn't
83
+ return the expected response object, it will be shown as an
84
+ error in a dialog to the user. So if the client is supposed to
85
+ handle the error in any way, for example, special form validation UX
86
+ like underlining the invalid field,
87
+ then the information about the error should be included in a valid
88
+ response object rather than raised as a python error.
89
+ """
90
+
91
+ """
92
+ File server to serve csv and video files.
93
+ FileResponse supports range requests for video buffering.
94
+ For security - only supports reading out of data_dir and model_dir
95
+ If we need to read out of other directories, they should be added to Project Info.
96
+ """
97
+
98
+
99
+ @app.get("/app/v0/files/{file_path:path}")
100
+ def read_file(file_path: Path):
101
+ # Prevent secrets like /etc/passwd and ~/.ssh/ from being leaked.
102
+ if file_path.suffix not in (".csv", ".mp4"):
103
+ raise HTTPException(
104
+ status_code=status.HTTP_403_FORBIDDEN,
105
+ detail="Only csv and mp4 files are supported.",
106
+ )
107
+ file_path = Path("/") / file_path
108
+
109
+ # Only capable of returning files that exist (not directories).
110
+ if not file_path.is_file():
111
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
112
+
113
+ return FileResponse(file_path)
114
+
115
+
116
+ ###########################################################################
117
+ # Serving angular
118
+ #
119
+ # In dev mode, `ng serve` serves ng, and proxies to us for backend requests.
120
+ # In production mode, we will serve ng.
121
+ # This is necessary to use HTTP2 for faster concurrent request performance (ng serve doesn't support it).
122
+ ###########################################################################
123
+
124
+
125
+ # Serve ng assets (js, css)
126
+ STATIC_DIR = Path(__file__).parent / "ngdist" / "ng_app"
127
+ if not STATIC_DIR.is_dir():
128
+ message = dedent(
129
+ """
130
+ ⚠️ Warning: We couldn't find the necessary static assets (like HTML, CSS, JavaScript files).
131
+ As a result, only the HTTP API is currently running.
132
+
133
+ This usually happens if you've cloned the source code directly.
134
+ To fix this and get the full application working, you'll need to either:
135
+
136
+ - Build the application: Refer to development.md in the repository for steps.
137
+ - Copy static files: Obtain these files from a PyPI source distribution of a released
138
+ version and place them in:
139
+
140
+ {STATIC_DIR}
141
+ """
142
+ )
143
+ # print(f'{Fore.white}{Back.yellow}{message}{Style.reset}', file=sys.stderr)
144
+ print(f"{message}", file=sys.stderr)
145
+
146
+ app.mount("/static", StaticFiles(directory=STATIC_DIR, check_dir=False), name="static")
147
+
148
+
149
+ @app.get("/favicon.ico")
150
+ async def favicon():
151
+ return FileResponse(Path(__file__).parent / "ngdist" / "ng_app" / "favicon.ico")
152
+
153
+
154
+ # Catch-all route. serve index.html.
155
+ @app.get("/{full_path:path}")
156
+ async def index():
157
+ return FileResponse(Path(__file__).parent / "ngdist" / "ng_app" / "index.html")
158
+
159
+
160
+ def run_app(host: str, port: int):
161
+ uvicorn.run(app, host=host, port=port)
@@ -341,17 +341,17 @@ License: "Apache-2.0"
341
341
  Package: tslib
342
342
  License: "0BSD"
343
343
 
344
- Copyright (c) Microsoft Corporation.
345
-
346
- Permission to use, copy, modify, and/or distribute this software for any
347
- purpose with or without fee is hereby granted.
348
-
349
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
350
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
351
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
352
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
353
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
354
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
344
+ Copyright (c) Microsoft Corporation.
345
+
346
+ Permission to use, copy, modify, and/or distribute this software for any
347
+ purpose with or without fee is hereby granted.
348
+
349
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
350
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
351
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
352
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
353
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
354
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
355
355
  PERFORMANCE OF THIS SOFTWARE.
356
356
  --------------------------------------------------------------------------------
357
357
  Package: @angular/common
@@ -329,6 +329,7 @@
329
329
  --color-green-400: oklch(79.2% 0.209 151.711);
330
330
  --color-sky-100: oklch(95.1% 0.026 236.824);
331
331
  --color-sky-700: oklch(50% 0.134 242.749);
332
+ --color-gray-400: oklch(70.7% 0.022 261.325);
332
333
  --color-black: #000;
333
334
  --spacing: 0.25rem;
334
335
  --container-xs: 20rem;
@@ -444,8 +445,8 @@
444
445
  }
445
446
  }
446
447
  }
447
- </style><link rel="stylesheet" href="/static/styles-4V6RXJMC.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="/static/styles-4V6RXJMC.css"></noscript></head>
448
+ </style><link rel="stylesheet" href="/static/styles-GMK322VW.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="/static/styles-GMK322VW.css"></noscript></head>
448
449
  <body>
449
450
  <app-root></app-root>
450
- <script src="/static/main-LJHMLKBL.js" type="module"></script></body>
451
+ <script src="/static/main-6XYUWDGZ.js" type="module"></script></body>
451
452
  </html>