chapkit 2.3.0__tar.gz → 2.3.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 (93) hide show
  1. {chapkit-2.3.0 → chapkit-2.3.1}/PKG-INFO +1 -1
  2. {chapkit-2.3.0 → chapkit-2.3.1}/pyproject.toml +1 -1
  3. {chapkit-2.3.0 → chapkit-2.3.1}/pyproject.toml.orig +1 -1
  4. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/manifest.json +1 -1
  5. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/run.py +79 -64
  6. {chapkit-2.3.0 → chapkit-2.3.1}/README.md +0 -0
  7. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/__init__.py +0 -0
  8. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/alembic/__init__.py +0 -0
  9. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/alembic/env.py +0 -0
  10. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/alembic/script.py.mako +0 -0
  11. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/alembic/versions/20251010_0927_4d869b5fb06e_initial_schema.py +0 -0
  12. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/alembic/versions/__init__.py +0 -0
  13. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/alembic_helpers.py +0 -0
  14. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/__init__.py +0 -0
  15. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/dist-Bd60saQK.js +0 -0
  16. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/index-CFWL4GpM.js +0 -0
  17. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/index-HY9NoZeE.css +0 -0
  18. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/javascript-CppQSorr.js +0 -0
  19. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/roboto-cyrillic-ext-wght-normal-BCp_3N7R.woff2 +0 -0
  20. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/roboto-cyrillic-wght-normal-fOt7epNf.woff2 +0 -0
  21. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/roboto-greek-ext-wght-normal-Bgw4czJT.woff2 +0 -0
  22. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/roboto-greek-wght-normal-DlT9XTxE.woff2 +0 -0
  23. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/roboto-latin-ext-wght-normal-DYIxWhlt.woff2 +0 -0
  24. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/roboto-latin-wght-normal-ccAYIvAh.woff2 +0 -0
  25. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/roboto-math-wght-normal-4a3xuzGk.woff2 +0 -0
  26. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/roboto-symbols-wght-normal-B7iHSfaG.woff2 +0 -0
  27. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/roboto-vietnamese-wght-normal-_f1XEJpo.woff2 +0 -0
  28. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/vitesse-dark-BZCL-v6S.js +0 -0
  29. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/assets/vitesse-light-VbXTXTou.js +0 -0
  30. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/favicon.svg +0 -0
  31. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/icons.svg +0 -0
  32. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/apps/console/index.html +0 -0
  33. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/dependencies.py +0 -0
  34. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/api/service_builder.py +0 -0
  35. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/artifact/__init__.py +0 -0
  36. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/artifact/manager.py +0 -0
  37. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/artifact/models.py +0 -0
  38. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/artifact/repository.py +0 -0
  39. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/artifact/router.py +0 -0
  40. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/artifact/schemas.py +0 -0
  41. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/__init__.py +0 -0
  42. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/__main__.py +0 -0
  43. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/artifact.py +0 -0
  44. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/cli.py +0 -0
  45. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/init.py +0 -0
  46. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/migrate.py +0 -0
  47. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/mlproject.py +0 -0
  48. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/requirements.py +0 -0
  49. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/.dockerignore +0 -0
  50. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/.gitignore +0 -0
  51. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/.python-version +0 -0
  52. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/Dockerfile.jinja2 +0 -0
  53. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/README.md.jinja2 +0 -0
  54. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/compose.yml.jinja2 +0 -0
  55. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/main.py.jinja2 +0 -0
  56. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/main_shell.py.jinja2 +0 -0
  57. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/migrate/CHAPKIT_migrate.md.jinja2 +0 -0
  58. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/migrate/Dockerfile_migrate.jinja2 +0 -0
  59. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/migrate/Makefile_migrate.jinja2 +0 -0
  60. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/migrate/README_migrate.md.jinja2 +0 -0
  61. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/migrate/dockerignore_migrate.jinja2 +0 -0
  62. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/migrate/gitignore_migrate.jinja2 +0 -0
  63. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/migrate/main_migrate.py.jinja2 +0 -0
  64. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/migrate/pyproject_migrate.toml.jinja2 +0 -0
  65. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/migrate/requirements_migrate.txt.jinja2 +0 -0
  66. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/publish-docker.yml.jinja2 +0 -0
  67. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/pyproject.toml.jinja2 +0 -0
  68. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/scripts/predict.R.jinja2 +0 -0
  69. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/scripts/predict_model.py.jinja2 +0 -0
  70. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/scripts/train.R.jinja2 +0 -0
  71. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/templates/scripts/train_model.py.jinja2 +0 -0
  72. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/test/__init__.py +0 -0
  73. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/test/command.py +0 -0
  74. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/test/runner.py +0 -0
  75. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/cli/test/utils.py +0 -0
  76. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/config/__init__.py +0 -0
  77. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/config/manager.py +0 -0
  78. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/config/models.py +0 -0
  79. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/config/repository.py +0 -0
  80. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/config/router.py +0 -0
  81. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/config/schemas.py +0 -0
  82. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/data/__init__.py +0 -0
  83. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/data/dataframe.py +0 -0
  84. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/data/generator.py +0 -0
  85. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/data/geo.py +0 -0
  86. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/ml/__init__.py +0 -0
  87. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/ml/manager.py +0 -0
  88. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/ml/router.py +0 -0
  89. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/ml/runner.py +0 -0
  90. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/ml/schemas.py +0 -0
  91. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/py.typed +0 -0
  92. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/scheduler.py +0 -0
  93. {chapkit-2.3.0 → chapkit-2.3.1}/src/chapkit/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: chapkit
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: ML and data service modules built on servicekit - config, artifacts, and ML workflows
5
5
  Keywords: ml,machine-learning,data-science,artifacts,config,servicekit
6
6
  Author: Morten Hansen
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "chapkit"
3
- version = "2.3.0"
3
+ version = "2.3.1"
4
4
  description = "ML and data service modules built on servicekit - config, artifacts, and ML workflows"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "chapkit"
3
- version = "2.3.0"
3
+ version = "2.3.1"
4
4
  description = "ML and data service modules built on servicekit - config, artifacts, and ML workflows"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Morten Hansen", email = "morten@dhis2.org" }]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Chapkit Console",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "prefix": "/",
5
5
  "description": "Default web console for exploring and operating a chapkit service",
6
6
  "author": "Chapkit Team",
@@ -5,12 +5,12 @@ from __future__ import annotations
5
5
  import errno
6
6
  import os
7
7
  import socket
8
+ from dataclasses import dataclass
8
9
  from pathlib import Path
9
10
  from typing import TYPE_CHECKING, Annotated
10
11
 
11
12
  import typer
12
13
 
13
- from chapkit.cli.migrate import _TIDYVERSE_HINTS, _any_r_script_uses
14
14
  from chapkit.cli.mlproject import (
15
15
  MLProject,
16
16
  MLProjectError,
@@ -27,6 +27,11 @@ if TYPE_CHECKING:
27
27
  from chapkit.api import MLServiceInfo
28
28
 
29
29
 
30
+ # servicekit's registration settings; registration only happens when the orchestrator URL is set.
31
+ ORCHESTRATOR_URL_ENV = "SERVICEKIT_ORCHESTRATOR_URL"
32
+ ADVERTISED_PORT_ENV = "SERVICEKIT_PORT"
33
+
34
+
30
35
  def _parse_param_overrides(raw: list[str] | None) -> dict[str, str]:
31
36
  """Parse repeated --param NAME=FILENAME flags into a mapping."""
32
37
  overrides: dict[str, str] = {}
@@ -60,63 +65,6 @@ def _warn_about_env(mlproject: MLProject) -> None:
60
65
  typer.echo("", err=True)
61
66
 
62
67
 
63
- def _suggest_chapkit_image(project_dir: Path, mlproject: MLProject) -> str:
64
- """Pick the chapkit-images base image best suited to this MLproject.
65
-
66
- Light-touch variant of migrate's detect_base_image - just returns the
67
- `chapkit-py` / `chapkit-r` / `chapkit-r-tidyverse` / `chapkit-r-inla`
68
- suffix so we can print a ready-made `docker run` one-liner. R + INLA
69
- detection mirrors migrate: `library(INLA)` / `library(fmesher)` in any
70
- root-level R script, or a `docker_r_inla` image in the MLproject's
71
- docker_env. Tidyverse detection reuses migrate's _TIDYVERSE_HINTS list.
72
- """
73
- has_r = any(project_dir.glob("*.r")) or any(project_dir.glob("*.R"))
74
- has_py = any(project_dir.glob("*.py"))
75
- # Mixed R + Python at the project root: only chapkit-r-inla bundles both
76
- # runtimes. Mirrors migrate.detect_base_image's mixed-language branch so the
77
- # `docker run` hint matches what `chapkit mlproject migrate` would build.
78
- if has_r and has_py:
79
- return "chapkit-r-inla"
80
- docker_env_image = mlproject.env_hints.get("docker_env", "")
81
- # Match migrate.detect_base_image's substring check so the hint and the
82
- # actual migrated image agree on inputs like `docker_r_inla:master` or any
83
- # other registry path that still mentions docker_r_inla.
84
- uses_inla = "docker_r_inla" in docker_env_image
85
- if not uses_inla and has_r:
86
- uses_inla = _any_r_script_uses(project_dir, ("INLA", "fmesher", "inla"))
87
- if has_r and uses_inla:
88
- return "chapkit-r-inla"
89
- if has_r and _any_r_script_uses(project_dir, _TIDYVERSE_HINTS):
90
- return "chapkit-r-tidyverse"
91
- if has_r:
92
- return "chapkit-r"
93
- if has_py:
94
- return "chapkit-py"
95
- # Ambiguous (no .r/.R/.py at root); default to Python - works for MLprojects that
96
- # call into compiled binaries or do all work inside the entry-point commands.
97
- return "chapkit-py"
98
-
99
-
100
- def _print_docker_hint(project_dir: Path, mlproject: MLProject, port: int) -> None:
101
- """Tell the user how to run the same MLproject via the prebuilt chapkit-images.
102
-
103
- Skipped when the host already looks like a chapkit container so we don't nest
104
- the hint inside itself.
105
- """
106
- if Path("/app/.venv/bin/chapkit").exists():
107
- return
108
- image = _suggest_chapkit_image(project_dir, mlproject)
109
- platform_flag = " --platform=linux/amd64" if image == "chapkit-r-inla" else ""
110
- typer.echo("")
111
- typer.echo("Tip: to run the same MLproject in Docker (no local R/Python env needed):")
112
- typer.echo(
113
- f" docker run --rm -p {port}:8000{platform_flag} -v {project_dir}:/work ghcr.io/dhis2-chap/{image}:latest"
114
- )
115
- typer.echo(" # chapkit-images ship WORKDIR=/work + a preinstalled chapkit; model-specific R / Python")
116
- typer.echo(" # packages need to be installed separately (e.g. `chapkit mlproject migrate` + `docker build`).")
117
- typer.echo("")
118
-
119
-
120
68
  def _check_port_available(host: str, port: int) -> str | None:
121
69
  """Return why host:port cannot be bound, or None when it is free."""
122
70
  try:
@@ -155,13 +103,50 @@ def _print_warnings(issues: list[str]) -> None:
155
103
  typer.echo(f"WARNING: {issue}", err=True)
156
104
 
157
105
 
106
+ @dataclass(frozen=True, slots=True)
107
+ class Registration:
108
+ """Where and how an mlproject service registers itself with an orchestrator such as chap-core."""
109
+
110
+ orchestrator_url: str
111
+ advertised_host: str | None
112
+ advertised_port: int | None
113
+ local_port: int | None
114
+
115
+
116
+ def resolve_registration(
117
+ port: int | None,
118
+ register_url: str | None = None,
119
+ advertise_host: str | None = None,
120
+ advertise_port: int | None = None,
121
+ ) -> Registration | None:
122
+ """Resolve registration settings from CLI options with SERVICEKIT_* fallbacks, or None when not configured.
123
+
124
+ Each option falls back to its environment variable (SERVICEKIT_ORCHESTRATOR_URL,
125
+ SERVICEKIT_HOST, SERVICEKIT_PORT). Without an advertised port servicekit would
126
+ advertise 8000, so the port the service listens on is advertised instead; that port
127
+ is also probed for readiness before registering.
128
+ """
129
+ orchestrator_url = register_url or os.getenv(ORCHESTRATOR_URL_ENV)
130
+ if not orchestrator_url:
131
+ return None
132
+ if advertise_port is None and not os.getenv(ADVERTISED_PORT_ENV):
133
+ advertise_port = port
134
+ # None host / port lets servicekit read SERVICEKIT_HOST / SERVICEKIT_PORT (or the hostname).
135
+ return Registration(orchestrator_url, advertise_host, advertise_port, port)
136
+
137
+
158
138
  def build_mlproject_app(
159
139
  mlproject: MLProject,
160
140
  overrides: dict[str, str] | None = None,
161
141
  issues: list[str] | None = None,
142
+ port: int | None = None,
143
+ registration: Registration | None = None,
162
144
  ) -> tuple[FastAPI, MLServiceInfo]:
163
145
  """Build the chapkit service for a parsed MLproject without starting a server.
164
146
 
147
+ With `registration` the service registers itself with that orchestrator (chap-core)
148
+ on startup; by default it is resolved from the SERVICEKIT_* environment variables.
149
+
165
150
  Must be called with the project directory as the working directory: ShellModelRunner
166
151
  copies the current directory into each train/predict workspace.
167
152
  """
@@ -196,13 +181,22 @@ def build_mlproject_app(
196
181
  level_labels={0: "ml_training_workspace", 1: "ml_prediction"},
197
182
  )
198
183
 
199
- app = MLServiceBuilder(
184
+ builder = MLServiceBuilder(
200
185
  info=info,
201
186
  config_schema=config_schema,
202
187
  hierarchy=hierarchy,
203
188
  runner=runner,
204
- ).build()
205
- return app, info
189
+ )
190
+ if registration is None:
191
+ registration = resolve_registration(port)
192
+ if registration is not None:
193
+ builder = builder.with_registration(
194
+ orchestrator_url=registration.orchestrator_url,
195
+ host=registration.advertised_host,
196
+ port=registration.advertised_port,
197
+ local_port=registration.local_port,
198
+ )
199
+ return builder.build(), info
206
200
 
207
201
 
208
202
  def run_command(
@@ -233,6 +227,26 @@ def run_command(
233
227
  ),
234
228
  ),
235
229
  ] = None,
230
+ register_url: Annotated[
231
+ str | None,
232
+ typer.Option(
233
+ help=(
234
+ "Register with this orchestrator on startup, e.g. http://chap:8000/v2/services/$register. "
235
+ "Defaults to SERVICEKIT_ORCHESTRATOR_URL; without either, nothing is registered. "
236
+ "A shared secret is read from SERVICEKIT_REGISTRATION_KEY."
237
+ ),
238
+ ),
239
+ ] = None,
240
+ advertise_host: Annotated[
241
+ str | None,
242
+ typer.Option(help="Host the orchestrator should call. Defaults to SERVICEKIT_HOST, then the hostname."),
243
+ ] = None,
244
+ advertise_port: Annotated[
245
+ int | None,
246
+ typer.Option(
247
+ help="Port the orchestrator should call. Defaults to SERVICEKIT_PORT, then --port.", min=1, max=65535
248
+ ),
249
+ ] = None,
236
250
  ) -> None:
237
251
  """Run an MLproject as a chapkit service."""
238
252
  overrides = _parse_param_overrides(param)
@@ -258,7 +272,8 @@ def run_command(
258
272
  os.chdir(project_dir)
259
273
  issues: list[str] = []
260
274
  try:
261
- app, info = build_mlproject_app(mlproject, overrides, issues)
275
+ registration = resolve_registration(port, register_url, advertise_host, advertise_port)
276
+ app, info = build_mlproject_app(mlproject, overrides, issues, port=port, registration=registration)
262
277
  except MLProjectError as error:
263
278
  typer.echo(f"Error: {error}", err=True)
264
279
  raise typer.Exit(code=1) from error
@@ -268,10 +283,10 @@ def run_command(
268
283
  typer.echo(f" train: {train_command}")
269
284
  typer.echo(f" predict: {predict_command}")
270
285
  _print_contract(info)
286
+ if registration is not None:
287
+ typer.echo(f" registers with: {registration.orchestrator_url}")
271
288
  _print_warnings(issues)
272
289
 
273
- _print_docker_hint(project_dir, mlproject, port)
274
-
275
290
  from chapkit.api import run_app
276
291
 
277
292
  run_app(app, host=host, port=port)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes