upnext-server 0.0.1__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 (170) hide show
  1. upnext_server-0.0.1/.gitignore +111 -0
  2. upnext_server-0.0.1/Dockerfile +54 -0
  3. upnext_server-0.0.1/PKG-INFO +17 -0
  4. upnext_server-0.0.1/README.md +0 -0
  5. upnext_server-0.0.1/alembic/README +1 -0
  6. upnext_server-0.0.1/alembic/env.py +89 -0
  7. upnext_server-0.0.1/alembic/script.py.mako +28 -0
  8. upnext_server-0.0.1/alembic/versions/3f2b7a9d4c1e_add_pending_artifacts_table.py +53 -0
  9. upnext_server-0.0.1/alembic/versions/6c7d9e2f1a10_add_job_lineage_columns.py +48 -0
  10. upnext_server-0.0.1/alembic/versions/9f3a1d4c2b88_add_job_function_name.py +48 -0
  11. upnext_server-0.0.1/alembic/versions/eb93a8d16e01_initial_migration.py +75 -0
  12. upnext_server-0.0.1/alembic.ini +151 -0
  13. upnext_server-0.0.1/pyproject.toml +41 -0
  14. upnext_server-0.0.1/src/server/__init__.py +5 -0
  15. upnext_server-0.0.1/src/server/config.py +63 -0
  16. upnext_server-0.0.1/src/server/db/__init__.py +17 -0
  17. upnext_server-0.0.1/src/server/db/models.py +181 -0
  18. upnext_server-0.0.1/src/server/db/repository.py +607 -0
  19. upnext_server-0.0.1/src/server/db/session.py +167 -0
  20. upnext_server-0.0.1/src/server/main.py +172 -0
  21. upnext_server-0.0.1/src/server/routes/__init__.py +28 -0
  22. upnext_server-0.0.1/src/server/routes/_shared/__init__.py +11 -0
  23. upnext_server-0.0.1/src/server/routes/_shared/stream_utils.py +35 -0
  24. upnext_server-0.0.1/src/server/routes/apis/__init__.py +58 -0
  25. upnext_server-0.0.1/src/server/routes/apis/apis_root.py +177 -0
  26. upnext_server-0.0.1/src/server/routes/apis/apis_stream.py +321 -0
  27. upnext_server-0.0.1/src/server/routes/apis/apis_utils.py +136 -0
  28. upnext_server-0.0.1/src/server/routes/artifacts/__init__.py +44 -0
  29. upnext_server-0.0.1/src/server/routes/artifacts/artifacts_root.py +90 -0
  30. upnext_server-0.0.1/src/server/routes/artifacts/artifacts_stream.py +62 -0
  31. upnext_server-0.0.1/src/server/routes/artifacts/artifacts_utils.py +56 -0
  32. upnext_server-0.0.1/src/server/routes/artifacts/job_artifacts.py +181 -0
  33. upnext_server-0.0.1/src/server/routes/dashboard.py +145 -0
  34. upnext_server-0.0.1/src/server/routes/events/__init__.py +36 -0
  35. upnext_server-0.0.1/src/server/routes/events/events_root.py +76 -0
  36. upnext_server-0.0.1/src/server/routes/events/events_stream.py +59 -0
  37. upnext_server-0.0.1/src/server/routes/functions.py +264 -0
  38. upnext_server-0.0.1/src/server/routes/health.py +24 -0
  39. upnext_server-0.0.1/src/server/routes/jobs/__init__.py +36 -0
  40. upnext_server-0.0.1/src/server/routes/jobs/jobs_root.py +335 -0
  41. upnext_server-0.0.1/src/server/routes/jobs/jobs_stream.py +107 -0
  42. upnext_server-0.0.1/src/server/routes/jobs/jobs_utils.py +30 -0
  43. upnext_server-0.0.1/src/server/routes/workers/__init__.py +30 -0
  44. upnext_server-0.0.1/src/server/routes/workers/workers_root.py +83 -0
  45. upnext_server-0.0.1/src/server/routes/workers/workers_stream.py +90 -0
  46. upnext_server-0.0.1/src/server/routes/workers/workers_utils.py +21 -0
  47. upnext_server-0.0.1/src/server/services/__init__.py +36 -0
  48. upnext_server-0.0.1/src/server/services/api_instances.py +40 -0
  49. upnext_server-0.0.1/src/server/services/api_tracking.py +251 -0
  50. upnext_server-0.0.1/src/server/services/cleanup.py +155 -0
  51. upnext_server-0.0.1/src/server/services/event_processing.py +537 -0
  52. upnext_server-0.0.1/src/server/services/queue.py +33 -0
  53. upnext_server-0.0.1/src/server/services/redis.py +47 -0
  54. upnext_server-0.0.1/src/server/services/stream_subscriber.py +448 -0
  55. upnext_server-0.0.1/src/server/services/workers.py +119 -0
  56. upnext_server-0.0.1/static/assets/index-DDih2EKO.js +166 -0
  57. upnext_server-0.0.1/static/assets/index-DzWC6DH7.css +1 -0
  58. upnext_server-0.0.1/static/index.html +14 -0
  59. upnext_server-0.0.1/static/vite.svg +1 -0
  60. upnext_server-0.0.1/tests/conftest.py +33 -0
  61. upnext_server-0.0.1/tests/test_cleanup_service.py +77 -0
  62. upnext_server-0.0.1/tests/test_cross_cutting.py +353 -0
  63. upnext_server-0.0.1/tests/test_event_processing.py +301 -0
  64. upnext_server-0.0.1/tests/test_events_stream_route.py +97 -0
  65. upnext_server-0.0.1/tests/test_main_config_and_services.py +331 -0
  66. upnext_server-0.0.1/tests/test_repository.py +196 -0
  67. upnext_server-0.0.1/tests/test_routes.py +765 -0
  68. upnext_server-0.0.1/tests/test_routes_apis_artifacts_functions.py +911 -0
  69. upnext_server-0.0.1/tests/test_services_metrics_workers_queue.py +244 -0
  70. upnext_server-0.0.1/tests/test_stream_pipeline_integration.py +110 -0
  71. upnext_server-0.0.1/tests/test_stream_pipeline_real_integration.py +227 -0
  72. upnext_server-0.0.1/tests/test_stream_subscriber.py +445 -0
  73. upnext_server-0.0.1/web/.gitignore +24 -0
  74. upnext_server-0.0.1/web/README.md +73 -0
  75. upnext_server-0.0.1/web/bun.lock +1422 -0
  76. upnext_server-0.0.1/web/components.json +23 -0
  77. upnext_server-0.0.1/web/eslint.config.js +35 -0
  78. upnext_server-0.0.1/web/index.html +13 -0
  79. upnext_server-0.0.1/web/package.json +65 -0
  80. upnext_server-0.0.1/web/playwright.config.ts +19 -0
  81. upnext_server-0.0.1/web/public/vite.svg +1 -0
  82. upnext_server-0.0.1/web/src/assets/react.svg +1 -0
  83. upnext_server-0.0.1/web/src/components/layout/index.ts +1 -0
  84. upnext_server-0.0.1/web/src/components/layout/sidebar.tsx +52 -0
  85. upnext_server-0.0.1/web/src/components/providers/event-stream-provider.test.tsx +477 -0
  86. upnext_server-0.0.1/web/src/components/providers/event-stream-provider.tsx +577 -0
  87. upnext_server-0.0.1/web/src/components/shared/api-requests-table.test.tsx +39 -0
  88. upnext_server-0.0.1/web/src/components/shared/api-requests-table.tsx +176 -0
  89. upnext_server-0.0.1/web/src/components/shared/index.ts +7 -0
  90. upnext_server-0.0.1/web/src/components/shared/jobs-table-panel.tsx +84 -0
  91. upnext_server-0.0.1/web/src/components/shared/jobs-table.test.tsx +45 -0
  92. upnext_server-0.0.1/web/src/components/shared/jobs-table.tsx +153 -0
  93. upnext_server-0.0.1/web/src/components/shared/panel.tsx +24 -0
  94. upnext_server-0.0.1/web/src/components/shared/progress-bar.tsx +53 -0
  95. upnext_server-0.0.1/web/src/components/shared/status-badge.tsx +27 -0
  96. upnext_server-0.0.1/web/src/components/shared/status-icon.tsx +35 -0
  97. upnext_server-0.0.1/web/src/components/ui/avatar.tsx +107 -0
  98. upnext_server-0.0.1/web/src/components/ui/badge.tsx +48 -0
  99. upnext_server-0.0.1/web/src/components/ui/button.tsx +64 -0
  100. upnext_server-0.0.1/web/src/components/ui/card.tsx +92 -0
  101. upnext_server-0.0.1/web/src/components/ui/chart.tsx +355 -0
  102. upnext_server-0.0.1/web/src/components/ui/progress.tsx +31 -0
  103. upnext_server-0.0.1/web/src/components/ui/scroll-area.tsx +56 -0
  104. upnext_server-0.0.1/web/src/components/ui/select.tsx +188 -0
  105. upnext_server-0.0.1/web/src/components/ui/separator.tsx +28 -0
  106. upnext_server-0.0.1/web/src/components/ui/skeleton.tsx +13 -0
  107. upnext_server-0.0.1/web/src/components/ui/table.tsx +114 -0
  108. upnext_server-0.0.1/web/src/components/ui/tabs.tsx +89 -0
  109. upnext_server-0.0.1/web/src/components/ui/tooltip.tsx +55 -0
  110. upnext_server-0.0.1/web/src/hooks/use-animated-number.test.tsx +29 -0
  111. upnext_server-0.0.1/web/src/hooks/use-animated-number.ts +87 -0
  112. upnext_server-0.0.1/web/src/hooks/use-event-source.test.tsx +118 -0
  113. upnext_server-0.0.1/web/src/hooks/use-event-source.ts +147 -0
  114. upnext_server-0.0.1/web/src/index.css +177 -0
  115. upnext_server-0.0.1/web/src/lib/env.ts +15 -0
  116. upnext_server-0.0.1/web/src/lib/types.ts +345 -0
  117. upnext_server-0.0.1/web/src/lib/upnext-api.test.ts +41 -0
  118. upnext_server-0.0.1/web/src/lib/upnext-api.ts +230 -0
  119. upnext_server-0.0.1/web/src/lib/utils.ts +67 -0
  120. upnext_server-0.0.1/web/src/main.tsx +80 -0
  121. upnext_server-0.0.1/web/src/routeTree.gen.ts +210 -0
  122. upnext_server-0.0.1/web/src/routes/__root.tsx +95 -0
  123. upnext_server-0.0.1/web/src/routes/apis/$name/-components/api-live-requests-panel.tsx +37 -0
  124. upnext_server-0.0.1/web/src/routes/apis/$name/-components/route-tree-panel.tsx +108 -0
  125. upnext_server-0.0.1/web/src/routes/apis/$name/-index.test.tsx +124 -0
  126. upnext_server-0.0.1/web/src/routes/apis/$name/index.tsx +187 -0
  127. upnext_server-0.0.1/web/src/routes/apis/-components/apis-table.test.tsx +55 -0
  128. upnext_server-0.0.1/web/src/routes/apis/-components/apis-table.tsx +114 -0
  129. upnext_server-0.0.1/web/src/routes/apis/-components/skeletons.tsx +50 -0
  130. upnext_server-0.0.1/web/src/routes/apis/-index.test.tsx +95 -0
  131. upnext_server-0.0.1/web/src/routes/apis/index.tsx +154 -0
  132. upnext_server-0.0.1/web/src/routes/dashboard/-components/api-trends-panel.tsx +197 -0
  133. upnext_server-0.0.1/web/src/routes/dashboard/-components/live-activity-panel.tsx +104 -0
  134. upnext_server-0.0.1/web/src/routes/dashboard/-components/skeletons.tsx +18 -0
  135. upnext_server-0.0.1/web/src/routes/dashboard/-components/system-overview-panel.tsx +115 -0
  136. upnext_server-0.0.1/web/src/routes/dashboard/-components/trends-panel.tsx +222 -0
  137. upnext_server-0.0.1/web/src/routes/dashboard/index.tsx +94 -0
  138. upnext_server-0.0.1/web/src/routes/functions/$name/-components/config-item.tsx +22 -0
  139. upnext_server-0.0.1/web/src/routes/functions/$name/-components/job-trends-panel.tsx +192 -0
  140. upnext_server-0.0.1/web/src/routes/functions/$name/-components/metric-card.tsx +23 -0
  141. upnext_server-0.0.1/web/src/routes/functions/$name/-components/skeletons.tsx +61 -0
  142. upnext_server-0.0.1/web/src/routes/functions/$name/index.tsx +191 -0
  143. upnext_server-0.0.1/web/src/routes/functions/-components/functions-table.tsx +97 -0
  144. upnext_server-0.0.1/web/src/routes/functions/-components/skeletons.tsx +62 -0
  145. upnext_server-0.0.1/web/src/routes/functions/-index.test.tsx +93 -0
  146. upnext_server-0.0.1/web/src/routes/functions/index.tsx +189 -0
  147. upnext_server-0.0.1/web/src/routes/index.tsx +24 -0
  148. upnext_server-0.0.1/web/src/routes/jobs/$jobId/-components/job-artifacts-tab.tsx +116 -0
  149. upnext_server-0.0.1/web/src/routes/jobs/$jobId/-components/job-details-panel.test.tsx +50 -0
  150. upnext_server-0.0.1/web/src/routes/jobs/$jobId/-components/job-details-panel.tsx +103 -0
  151. upnext_server-0.0.1/web/src/routes/jobs/$jobId/-components/job-logs-tab.tsx +123 -0
  152. upnext_server-0.0.1/web/src/routes/jobs/$jobId/-components/job-run-header.tsx +43 -0
  153. upnext_server-0.0.1/web/src/routes/jobs/$jobId/-components/job-task-runs-tab.tsx +84 -0
  154. upnext_server-0.0.1/web/src/routes/jobs/$jobId/-components/job-timeline-panel.tsx +143 -0
  155. upnext_server-0.0.1/web/src/routes/jobs/$jobId/-components/skeletons.tsx +33 -0
  156. upnext_server-0.0.1/web/src/routes/jobs/$jobId/-components/timeline-model.test.ts +124 -0
  157. upnext_server-0.0.1/web/src/routes/jobs/$jobId/-components/timeline-model.ts +216 -0
  158. upnext_server-0.0.1/web/src/routes/jobs/$jobId/-index.test.tsx +111 -0
  159. upnext_server-0.0.1/web/src/routes/jobs/$jobId/index.tsx +127 -0
  160. upnext_server-0.0.1/web/src/routes/workers/-components/skeletons.tsx +42 -0
  161. upnext_server-0.0.1/web/src/routes/workers/-components/workers-table.tsx +97 -0
  162. upnext_server-0.0.1/web/src/routes/workers/-index.test.tsx +89 -0
  163. upnext_server-0.0.1/web/src/routes/workers/index.tsx +128 -0
  164. upnext_server-0.0.1/web/src/test/setup.ts +17 -0
  165. upnext_server-0.0.1/web/tests/e2e/env.ts +12 -0
  166. upnext_server-0.0.1/web/tests/e2e/job-detail.spec.ts +48 -0
  167. upnext_server-0.0.1/web/tsconfig.app.json +34 -0
  168. upnext_server-0.0.1/web/tsconfig.json +13 -0
  169. upnext_server-0.0.1/web/tsconfig.node.json +30 -0
  170. upnext_server-0.0.1/web/vite.config.ts +46 -0
@@ -0,0 +1,111 @@
1
+ # =============================================================================
2
+ # Python
3
+ # =============================================================================
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ *.so
8
+ .Python
9
+ build/
10
+ develop-eggs/
11
+ dist/
12
+ downloads/
13
+ eggs/
14
+ .eggs/
15
+ *.egg-info/
16
+ .installed.cfg
17
+ *.egg
18
+ MANIFEST
19
+ *.manifest
20
+ pip-log.txt
21
+ pip-delete-this-directory.txt
22
+ htmlcov/
23
+ .tox/
24
+ .nox/
25
+ .coverage
26
+ .coverage.*
27
+ .cache
28
+ nosetests.xml
29
+ coverage.xml
30
+ *.cover
31
+ *.py,cover
32
+ .hypothesis/
33
+ .pytest_cache/
34
+ cover/
35
+ *.mo
36
+ *.pot
37
+ .venv/
38
+ venv/
39
+ ENV/
40
+ env/
41
+ .pdm.toml
42
+ .pdm-python
43
+ .pdm-build/
44
+ __pypackages__/
45
+ .mypy_cache/
46
+ .dmypy.json
47
+ dmypy.json
48
+ .pyre/
49
+ .pytype/
50
+ cython_debug/
51
+ .ruff_cache/
52
+ .pypirc
53
+
54
+ # =============================================================================
55
+ # Node / Next.js
56
+ # =============================================================================
57
+ node_modules/
58
+ .pnp
59
+ .pnp.js
60
+ .next/
61
+ out/
62
+ next-env.d.ts
63
+ .source/
64
+ npm-debug.log*
65
+ yarn-debug.log*
66
+ yarn-error.log*
67
+ .pnpm-debug.log*
68
+ *.tsbuildinfo
69
+ .vercel
70
+
71
+ # =============================================================================
72
+ # Database
73
+ # =============================================================================
74
+ *.sqlite
75
+ *.sqlite3
76
+ *.sqlite3-journal
77
+ db.sqlite
78
+ db.sqlite3
79
+
80
+ # =============================================================================
81
+ # Environment & Secrets
82
+ # =============================================================================
83
+ .env
84
+ .env*.local
85
+ .env.prod
86
+
87
+ # =============================================================================
88
+ # IDE & OS
89
+ # =============================================================================
90
+ .DS_Store
91
+ *.pem
92
+ .idea/
93
+ *.swp
94
+ *.swo
95
+ *~
96
+
97
+ # =============================================================================
98
+ # Testing & Coverage
99
+ # =============================================================================
100
+ coverage/
101
+ coverage_html/
102
+ test-results/
103
+ playwright-report/
104
+ blob-report/
105
+ .vitest/
106
+
107
+ # =============================================================================
108
+ # tanstack start
109
+ # =============================================================================
110
+ .output/
111
+ .tanstack/
@@ -0,0 +1,54 @@
1
+ # UpNext Server - Multi-stage build
2
+
3
+ # Stage 1: Build frontend
4
+ FROM oven/bun:1 AS frontend-builder
5
+
6
+ WORKDIR /app/web
7
+
8
+ # Copy frontend package files
9
+ COPY packages/server/web/package.json packages/server/web/bun.lock* ./
10
+
11
+ # Install dependencies
12
+ RUN bun install --frozen-lockfile
13
+
14
+ # Copy frontend source
15
+ COPY packages/server/web ./
16
+
17
+ # Build frontend (outputs to ../static)
18
+ RUN bun run build && mv ../static /static
19
+
20
+ # Stage 2: Python server
21
+ FROM python:3.12-slim
22
+
23
+ WORKDIR /app
24
+
25
+ # Install system dependencies
26
+ RUN apt-get update && apt-get install -y --no-install-recommends \
27
+ libpq-dev \
28
+ curl \
29
+ && rm -rf /var/lib/apt/lists/*
30
+
31
+ # Install uv
32
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
33
+
34
+ # Copy workspace config and lock file
35
+ COPY pyproject.toml uv.lock ./
36
+
37
+ # Copy packages
38
+ COPY packages/shared ./packages/shared
39
+ COPY packages/server ./packages/server
40
+
41
+ # Copy built frontend
42
+ COPY --from=frontend-builder /static ./packages/server/static
43
+
44
+ # Install dependencies using workspace
45
+ RUN uv sync --frozen --no-dev --package upnext-server
46
+
47
+ # Set working directory to server package
48
+ WORKDIR /app/packages/server
49
+
50
+ # Expose port
51
+ EXPOSE 8080
52
+
53
+ # Start server
54
+ CMD ["uv", "run", "uvicorn", "server.main:app", "--host", "0.0.0.0", "--port", "8080"]
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: upnext-server
3
+ Version: 0.0.1
4
+ Summary: UpNext hosted API server
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: aiosqlite>=0.22.1
7
+ Requires-Dist: alembic>=1.14.0
8
+ Requires-Dist: asyncpg>=0.30.0
9
+ Requires-Dist: fastapi>=0.115.0
10
+ Requires-Dist: greenlet>=3.3.1
11
+ Requires-Dist: psycopg2-binary>=2.9.0
12
+ Requires-Dist: pydantic-settings>=2.12.0
13
+ Requires-Dist: pydantic>=2.10.0
14
+ Requires-Dist: redis>=7.1.0
15
+ Requires-Dist: sqlalchemy>=2.0.36
16
+ Requires-Dist: upnext-shared
17
+ Requires-Dist: uvicorn>=0.34.0
File without changes
@@ -0,0 +1 @@
1
+ Generic single-database configuration.
@@ -0,0 +1,89 @@
1
+ """Alembic environment configuration for UpNext migrations."""
2
+
3
+ import os
4
+ from logging.config import fileConfig
5
+
6
+ from alembic import context
7
+
8
+ # Import the models to register them with Base.metadata
9
+ from server.db.models import Base
10
+ from sqlalchemy import engine_from_config, pool
11
+
12
+ # this is the Alembic Config object, which provides
13
+ # access to the values within the .ini file in use.
14
+ config = context.config
15
+
16
+ # Interpret the config file for Python logging.
17
+ # This line sets up loggers basically.
18
+ if config.config_file_name is not None:
19
+ fileConfig(config.config_file_name)
20
+
21
+ # Set the target metadata for autogenerate support
22
+ target_metadata = Base.metadata
23
+
24
+
25
+ def get_url() -> str:
26
+ """Get database URL from environment or config."""
27
+ url = os.environ.get("UPNEXT_DATABASE_URL") or os.environ.get("DATABASE_URL")
28
+ if url:
29
+ # Convert asyncpg to psycopg2 for alembic (alembic doesn't support async)
30
+ if "+asyncpg" in url:
31
+ url = url.replace("+asyncpg", "+psycopg2")
32
+ elif "postgresql://" in url and "+psycopg2" not in url:
33
+ url = url.replace("postgresql://", "postgresql+psycopg2://")
34
+ return url
35
+ return config.get_main_option("sqlalchemy.url", "").replace("+asyncpg", "+psycopg2")
36
+
37
+
38
+ def run_migrations_offline() -> None:
39
+ """Run migrations in 'offline' mode.
40
+
41
+ This configures the context with just a URL
42
+ and not an Engine, though an Engine is acceptable
43
+ here as well. By skipping the Engine creation
44
+ we don't even need a DBAPI to be available.
45
+
46
+ Calls to context.execute() here emit the given string to the
47
+ script output.
48
+
49
+ """
50
+ url = get_url()
51
+ context.configure(
52
+ url=url,
53
+ target_metadata=target_metadata,
54
+ literal_binds=True,
55
+ dialect_opts={"paramstyle": "named"},
56
+ )
57
+
58
+ with context.begin_transaction():
59
+ context.run_migrations()
60
+
61
+
62
+ def run_migrations_online() -> None:
63
+ """Run migrations in 'online' mode.
64
+
65
+ In this scenario we need to create an Engine
66
+ and associate a connection with the context.
67
+
68
+ """
69
+ # Override the sqlalchemy.url with our computed URL
70
+ configuration = config.get_section(config.config_ini_section, {})
71
+ configuration["sqlalchemy.url"] = get_url()
72
+
73
+ connectable = engine_from_config(
74
+ configuration,
75
+ prefix="sqlalchemy.",
76
+ poolclass=pool.NullPool,
77
+ )
78
+
79
+ with connectable.connect() as connection:
80
+ context.configure(connection=connection, target_metadata=target_metadata)
81
+
82
+ with context.begin_transaction():
83
+ context.run_migrations()
84
+
85
+
86
+ if context.is_offline_mode():
87
+ run_migrations_offline()
88
+ else:
89
+ run_migrations_online()
@@ -0,0 +1,28 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ ${upgrades if upgrades else "pass"}
24
+
25
+
26
+ def downgrade() -> None:
27
+ """Downgrade schema."""
28
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,53 @@
1
+ """add pending artifacts table
2
+
3
+ Revision ID: 3f2b7a9d4c1e
4
+ Revises: eb93a8d16e01
5
+ Create Date: 2026-02-07 00:00:00.000000
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "3f2b7a9d4c1e"
16
+ down_revision: Union[str, Sequence[str], None] = "eb93a8d16e01"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ op.create_table(
24
+ "pending_artifacts",
25
+ sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
26
+ sa.Column("job_id", sa.String(length=36), nullable=False),
27
+ sa.Column("name", sa.String(length=255), nullable=False),
28
+ sa.Column("type", sa.String(length=50), nullable=False),
29
+ sa.Column("size_bytes", sa.Integer(), nullable=True),
30
+ sa.Column("data", sa.JSON(), nullable=True),
31
+ sa.Column("path", sa.String(length=500), nullable=True),
32
+ sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
33
+ sa.PrimaryKeyConstraint("id"),
34
+ )
35
+ op.create_index(
36
+ "ix_pending_artifacts_job_id",
37
+ "pending_artifacts",
38
+ ["job_id"],
39
+ unique=False,
40
+ )
41
+ op.create_index(
42
+ "ix_pending_artifacts_created_at",
43
+ "pending_artifacts",
44
+ ["created_at"],
45
+ unique=False,
46
+ )
47
+
48
+
49
+ def downgrade() -> None:
50
+ """Downgrade schema."""
51
+ op.drop_index("ix_pending_artifacts_created_at", table_name="pending_artifacts")
52
+ op.drop_index("ix_pending_artifacts_job_id", table_name="pending_artifacts")
53
+ op.drop_table("pending_artifacts")
@@ -0,0 +1,48 @@
1
+ """add job lineage columns
2
+
3
+ Revision ID: 6c7d9e2f1a10
4
+ Revises: 3f2b7a9d4c1e
5
+ Create Date: 2026-02-07 00:00:01.000000
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "6c7d9e2f1a10"
16
+ down_revision: Union[str, Sequence[str], None] = "3f2b7a9d4c1e"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ with op.batch_alter_table("job_history") as batch_op:
24
+ batch_op.add_column(sa.Column("parent_id", sa.String(length=36), nullable=True))
25
+ batch_op.add_column(sa.Column("root_id", sa.String(length=36), nullable=True))
26
+ batch_op.create_index(
27
+ "ix_job_history_parent_id", ["parent_id"], unique=False
28
+ )
29
+ batch_op.create_index("ix_job_history_root_id", ["root_id"], unique=False)
30
+
31
+ # Ensure all rows have a lineage root.
32
+ op.execute("UPDATE job_history SET root_id = id WHERE root_id IS NULL")
33
+
34
+ with op.batch_alter_table("job_history") as batch_op:
35
+ batch_op.alter_column(
36
+ "root_id",
37
+ existing_type=sa.String(length=36),
38
+ nullable=False,
39
+ )
40
+
41
+
42
+ def downgrade() -> None:
43
+ """Downgrade schema."""
44
+ with op.batch_alter_table("job_history") as batch_op:
45
+ batch_op.drop_index("ix_job_history_root_id")
46
+ batch_op.drop_index("ix_job_history_parent_id")
47
+ batch_op.drop_column("root_id")
48
+ batch_op.drop_column("parent_id")
@@ -0,0 +1,48 @@
1
+ """add job function_name column
2
+
3
+ Revision ID: 9f3a1d4c2b88
4
+ Revises: 6c7d9e2f1a10
5
+ Create Date: 2026-02-08 00:00:00.000000
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "9f3a1d4c2b88"
16
+ down_revision: Union[str, Sequence[str], None] = "6c7d9e2f1a10"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ with op.batch_alter_table("job_history") as batch_op:
24
+ batch_op.add_column(sa.Column("function_name", sa.String(length=255), nullable=True))
25
+
26
+ op.execute(
27
+ "UPDATE job_history SET function_name = function "
28
+ "WHERE function_name IS NULL OR function_name = ''"
29
+ )
30
+
31
+ with op.batch_alter_table("job_history") as batch_op:
32
+ batch_op.alter_column(
33
+ "function_name",
34
+ existing_type=sa.String(length=255),
35
+ nullable=False,
36
+ )
37
+ batch_op.create_index(
38
+ "ix_job_history_function_name",
39
+ ["function_name"],
40
+ unique=False,
41
+ )
42
+
43
+
44
+ def downgrade() -> None:
45
+ """Downgrade schema."""
46
+ with op.batch_alter_table("job_history") as batch_op:
47
+ batch_op.drop_index("ix_job_history_function_name")
48
+ batch_op.drop_column("function_name")
@@ -0,0 +1,75 @@
1
+ """initial migration
2
+
3
+ Revision ID: eb93a8d16e01
4
+ Revises:
5
+ Create Date: 2026-02-05 16:56:55.433739
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = 'eb93a8d16e01'
16
+ down_revision: Union[str, Sequence[str], None] = None
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ op.create_table('job_history',
25
+ sa.Column('id', sa.String(length=36), nullable=False),
26
+ sa.Column('function', sa.String(length=255), nullable=False),
27
+ sa.Column('status', sa.String(length=20), nullable=False),
28
+ sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
29
+ sa.Column('scheduled_at', sa.DateTime(timezone=True), nullable=True),
30
+ sa.Column('started_at', sa.DateTime(timezone=True), nullable=True),
31
+ sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
32
+ sa.Column('attempts', sa.Integer(), nullable=False),
33
+ sa.Column('max_retries', sa.Integer(), nullable=False),
34
+ sa.Column('timeout', sa.Float(), nullable=True),
35
+ sa.Column('worker_id', sa.String(length=64), nullable=True),
36
+ sa.Column('progress', sa.Float(), nullable=False),
37
+ sa.Column('kwargs', sa.JSON(), nullable=False),
38
+ sa.Column('metadata', sa.JSON(), nullable=False),
39
+ sa.Column('result', sa.JSON(), nullable=True),
40
+ sa.Column('error', sa.Text(), nullable=True),
41
+ sa.PrimaryKeyConstraint('id')
42
+ )
43
+ op.create_index('ix_job_history_created_at', 'job_history', ['created_at'], unique=False)
44
+ op.create_index('ix_job_history_function', 'job_history', ['function'], unique=False)
45
+ op.create_index('ix_job_history_status', 'job_history', ['status'], unique=False)
46
+ op.create_index('ix_job_history_worker_id', 'job_history', ['worker_id'], unique=False)
47
+ op.create_table('artifacts',
48
+ sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
49
+ sa.Column('job_id', sa.String(length=36), nullable=False),
50
+ sa.Column('name', sa.String(length=255), nullable=False),
51
+ sa.Column('type', sa.String(length=50), nullable=False),
52
+ sa.Column('size_bytes', sa.Integer(), nullable=True),
53
+ sa.Column('data', sa.JSON(), nullable=True),
54
+ sa.Column('path', sa.String(length=500), nullable=True),
55
+ sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
56
+ sa.ForeignKeyConstraint(['job_id'], ['job_history.id'], ondelete='CASCADE'),
57
+ sa.PrimaryKeyConstraint('id')
58
+ )
59
+ op.create_index('ix_artifacts_job_id', 'artifacts', ['job_id'], unique=False)
60
+ op.create_index('ix_artifacts_name', 'artifacts', ['name'], unique=False)
61
+ # ### end Alembic commands ###
62
+
63
+
64
+ def downgrade() -> None:
65
+ """Downgrade schema."""
66
+ # ### commands auto generated by Alembic - please adjust! ###
67
+ op.drop_index('ix_artifacts_name', table_name='artifacts')
68
+ op.drop_index('ix_artifacts_job_id', table_name='artifacts')
69
+ op.drop_table('artifacts')
70
+ op.drop_index('ix_job_history_worker_id', table_name='job_history')
71
+ op.drop_index('ix_job_history_status', table_name='job_history')
72
+ op.drop_index('ix_job_history_function', table_name='job_history')
73
+ op.drop_index('ix_job_history_created_at', table_name='job_history')
74
+ op.drop_table('job_history')
75
+ # ### end Alembic commands ###
@@ -0,0 +1,151 @@
1
+ # A generic, single database configuration.
2
+
3
+ [alembic]
4
+ # path to migration scripts.
5
+ # this is typically a path given in POSIX (e.g. forward slashes)
6
+ # format, relative to the token %(here)s which refers to the location of this
7
+ # ini file
8
+ script_location = %(here)s/alembic
9
+
10
+ # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
11
+ # Uncomment the line below if you want the files to be prepended with date and time
12
+ # see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
13
+ # for all available tokens
14
+ # file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
15
+ # Or organize into date-based subdirectories (requires recursive_version_locations = true)
16
+ # file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s
17
+
18
+ # sys.path path, will be prepended to sys.path if present.
19
+ # defaults to the current working directory. for multiple paths, the path separator
20
+ # is defined by "path_separator" below.
21
+ prepend_sys_path = src
22
+
23
+
24
+ # timezone to use when rendering the date within the migration file
25
+ # as well as the filename.
26
+ # If specified, requires the tzdata library which can be installed by adding
27
+ # `alembic[tz]` to the pip requirements.
28
+ # string value is passed to ZoneInfo()
29
+ # leave blank for localtime
30
+ # timezone =
31
+
32
+ # max length of characters to apply to the "slug" field
33
+ # truncate_slug_length = 40
34
+
35
+ # set to 'true' to run the environment during
36
+ # the 'revision' command, regardless of autogenerate
37
+ # revision_environment = false
38
+
39
+ # set to 'true' to allow .pyc and .pyo files without
40
+ # a source .py file to be detected as revisions in the
41
+ # versions/ directory
42
+ # sourceless = false
43
+
44
+ # version location specification; This defaults
45
+ # to <script_location>/versions. When using multiple version
46
+ # directories, initial revisions must be specified with --version-path.
47
+ # The path separator used here should be the separator specified by "path_separator"
48
+ # below.
49
+ # version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
50
+
51
+ # path_separator; This indicates what character is used to split lists of file
52
+ # paths, including version_locations and prepend_sys_path within configparser
53
+ # files such as alembic.ini.
54
+ # The default rendered in new alembic.ini files is "os", which uses os.pathsep
55
+ # to provide os-dependent path splitting.
56
+ #
57
+ # Note that in order to support legacy alembic.ini files, this default does NOT
58
+ # take place if path_separator is not present in alembic.ini. If this
59
+ # option is omitted entirely, fallback logic is as follows:
60
+ #
61
+ # 1. Parsing of the version_locations option falls back to using the legacy
62
+ # "version_path_separator" key, which if absent then falls back to the legacy
63
+ # behavior of splitting on spaces and/or commas.
64
+ # 2. Parsing of the prepend_sys_path option falls back to the legacy
65
+ # behavior of splitting on spaces, commas, or colons.
66
+ #
67
+ # Valid values for path_separator are:
68
+ #
69
+ # path_separator = :
70
+ # path_separator = ;
71
+ # path_separator = space
72
+ # path_separator = newline
73
+ #
74
+ # Use os.pathsep. Default configuration used for new projects.
75
+ path_separator = os
76
+
77
+ # set to 'true' to search source files recursively
78
+ # in each "version_locations" directory
79
+ # new in Alembic version 1.10
80
+ # recursive_version_locations = false
81
+
82
+ # the output encoding used when revision files
83
+ # are written from script.py.mako
84
+ # output_encoding = utf-8
85
+
86
+ # database URL. This is consumed by the user-maintained env.py script only.
87
+ # other means of configuring database URLs may be customized within the env.py
88
+ # file.
89
+ # Database URL - read from DATABASE_URL environment variable in env.py
90
+ # For local dev: postgresql+asyncpg://upnext:upnext@localhost:5432/upnext
91
+ sqlalchemy.url = postgresql+asyncpg://upnext:upnext@localhost:5432/upnext
92
+
93
+
94
+ [post_write_hooks]
95
+ # post_write_hooks defines scripts or Python functions that are run
96
+ # on newly generated revision scripts. See the documentation for further
97
+ # detail and examples
98
+
99
+ # format using "black" - use the console_scripts runner, against the "black" entrypoint
100
+ # hooks = black
101
+ # black.type = console_scripts
102
+ # black.entrypoint = black
103
+ # black.options = -l 79 REVISION_SCRIPT_FILENAME
104
+
105
+ # lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
106
+ # hooks = ruff
107
+ # ruff.type = module
108
+ # ruff.module = ruff
109
+ # ruff.options = check --fix REVISION_SCRIPT_FILENAME
110
+
111
+ # Alternatively, use the exec runner to execute a binary found on your PATH
112
+ # hooks = ruff
113
+ # ruff.type = exec
114
+ # ruff.executable = ruff
115
+ # ruff.options = check --fix REVISION_SCRIPT_FILENAME
116
+
117
+ # Logging configuration. This is also consumed by the user-maintained
118
+ # env.py script only.
119
+ [loggers]
120
+ keys = root,sqlalchemy,alembic
121
+
122
+ [handlers]
123
+ keys = console
124
+
125
+ [formatters]
126
+ keys = generic
127
+
128
+ [logger_root]
129
+ level = WARNING
130
+ handlers = console
131
+ qualname =
132
+
133
+ [logger_sqlalchemy]
134
+ level = WARNING
135
+ handlers =
136
+ qualname = sqlalchemy.engine
137
+
138
+ [logger_alembic]
139
+ level = INFO
140
+ handlers =
141
+ qualname = alembic
142
+
143
+ [handler_console]
144
+ class = StreamHandler
145
+ args = (sys.stderr,)
146
+ level = NOTSET
147
+ formatter = generic
148
+
149
+ [formatter_generic]
150
+ format = %(levelname)-5.5s [%(name)s] %(message)s
151
+ datefmt = %H:%M:%S