Shared service-tier scaffolding (FastAPI app factory, settings base, generic routes) for Juniper ML model services
Project description
juniper-service-core
The shared FastAPI service-tier framework every Juniper model service is built on.
Standing up a model as a service means writing the same plumbing every time: an app factory,
settings, health probes, API-key auth, Docker-secret loading, security middleware, a training
lifecycle with status tracking and event streaming, the HTTP routes that drive it, live WebSocket
streams, and — for distributed training — a worker pool. juniper-service-core is that plumbing,
factored out of juniper-cascor into one
model-agnostic package. You bring the model; it brings the service.
The -core suffix marks it as genuinely shared: it carries no model, training, or domain logic —
those stay in the owning service and are injected. A new Juniper model service (regression,
classification, whatever the model does) wires its model into the lifecycle and gets a consistent
HTTP + WebSocket surface for free.
Dependency-free top-level import
import juniper_service_core pulls in no third-party runtime dependency — only __version__
loads eagerly. The rest of the public surface (create_app, SettingsBase, and the security /
secrets / middleware / lifecycle / routes / websocket / worker helpers) resolves lazily on attribute
access (PEP 562 __getattr__), so fastapi / pydantic-settings / starlette / numpy load only
when you touch a name that needs them. That's what lets a --no-deps publish-verify import the
package cleanly.
Install
pip install juniper-service-core
What's in the box
| Subsystem | Import surface | What it gives you |
|---|---|---|
| App factory | create_app |
Model-agnostic FastAPI app: mounts the health router, then any service-supplied routers. |
| Settings | SettingsBase |
pydantic-settings base (service_name, host, port, log_level); subclass and set your own env_prefix. |
| Health | (mounted by create_app) |
GET /v1/health (liveness) + GET /v1/health/ready (readiness). |
| Security | APIKeyAuth, RateLimiter, build_api_key_auth, … |
X-API-Key authentication + rate limiting. |
| Secrets | get_secret |
Docker _FILE secret-indirection reader. |
| Middleware | SecurityMiddleware, SecurityHeadersMiddleware, RequestBodyLimitMiddleware |
Drop-in ASGI middleware. |
| Launcher | ManagedService, start_service, wait_for_health |
Subprocess service launcher (stdlib-only). |
| Lifecycle | TrainingLifecycle, ServiceLifecycleManager, … |
Drives a juniper-model-core TrainableModel through a status FSM + a TrainingEvent monitor; synchronous and threaded-orchestrator bodies, with snapshots + replay. |
| Generic routes | build_routers, ResponseEnvelope, … |
Training-control, metrics, dataset, network, and snapshot HTTP routes over the injected lifecycle. |
| WebSocket | attach_websocket, training_stream_handler, control_stream_handler, … |
Live training + control streams, plus a worker channel (/ws/workers). |
| Worker pool | WorkerCoordinator, WorkerRegistry, … |
Distributed-worker registration, coordination, and task dispatch (stdlib-only foundations). |
Import cost tracks the subsystem: .security, .secrets, .middleware, .launcher, and .workers
are stdlib-/lightweight; .lifecycle needs juniper-model-core; .routes and .websocket need
fastapi.
Quick start
A minimal, health-only service:
from juniper_service_core import create_app, SettingsBase
from pydantic_settings import SettingsConfigDict
class MyServiceSettings(SettingsBase):
model_config = SettingsConfigDict(env_prefix="JUNIPER_MYSVC_")
app = create_app(title="My Service", version="1.0.0")
# GET /v1/health -> {"status": "ok"}
# GET /v1/health/ready -> {"status": "ready"}
To serve a model, wrap it in a ServiceLifecycleManager, mount the generic routers, and expose
the lifecycle on app.state — the routes read it from there:
from juniper_service_core import create_app, ServiceLifecycleManager, build_routers
manager = ServiceLifecycleManager(MyModel()) # MyModel = any juniper-model-core TrainableModel
app = create_app(title="My Model Service", version="1.0.0", routers=build_routers())
app.state.lifecycle = manager
# -> POST /v1/train, GET /v1/training/status, and the metrics / dataset / network / snapshot
# routes, all driving your model. (Routes return 503 until app.state.lifecycle is wired.)
juniper-recurrence is the canonical worked
example — its FastAPI service is essentially this wiring around an LMU regressor.
Who uses it
- juniper-recurrence — the first real consumer;
its service is
create_app+ a lifecycle around the LMU regressor. - juniper-cascor — the framework was extracted from cascor's service tier; cascor's own cutover onto it is in progress.
Any new Juniper model service should build on this rather than re-implementing the plumbing.
Status
Live on PyPI (Beta). The current version is shown by the badge above; see
CHANGELOG.md for history. Pin with juniper-service-core>=0.2,<0.3.
Development
pip install -e ".[test]"
pytest tests/ -v
Design
Part of the Juniper ML research platform. The architecture and the
cascor extraction plan are the model/middleware refactor design of record
(notes/JUNIPER_MODEL_MIDDLEWARE_REFACTOR_DESIGN_AND_PLAN_2026-05-31.md in juniper-ml).
License
MIT — see LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file juniper_service_core-0.4.0.tar.gz.
File metadata
- Download URL: juniper_service_core-0.4.0.tar.gz
- Upload date:
- Size: 119.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
969a66247e4ac9139fccccf5e9ba59ca85e40f64f526c3241efcbd95b8a19b63
|
|
| MD5 |
ecbb669e70366e3cf7ee000ebf8001b7
|
|
| BLAKE2b-256 |
279ba68fbc2ecad85d4741a84254eef2b8eaf135de51df32c22d5fb8697a9a11
|
Provenance
The following attestation bundles were made for juniper_service_core-0.4.0.tar.gz:
Publisher:
publish-service-core.yml on pcalnon/juniper-ml
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
juniper_service_core-0.4.0.tar.gz -
Subject digest:
969a66247e4ac9139fccccf5e9ba59ca85e40f64f526c3241efcbd95b8a19b63 - Sigstore transparency entry: 2042362732
- Sigstore integration time:
-
Permalink:
pcalnon/juniper-ml@e5d9617a6d44e2dfbd93fa29d71f13419ea25eaa -
Branch / Tag:
refs/tags/juniper-service-core-v0.4.0 - Owner: https://github.com/pcalnon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-service-core.yml@e5d9617a6d44e2dfbd93fa29d71f13419ea25eaa -
Trigger Event:
release
-
Statement type:
File details
Details for the file juniper_service_core-0.4.0-py3-none-any.whl.
File metadata
- Download URL: juniper_service_core-0.4.0-py3-none-any.whl
- Upload date:
- Size: 112.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8549dd20261c8806643b1f260aa95b9e8a090f02be15ed19534a7bbef9d58ee5
|
|
| MD5 |
6f152edf3da2521e8a855f724c53b5eb
|
|
| BLAKE2b-256 |
224aacfa620b34bb634b6b0f72060d086c00dc075e1a3fb2d3c3c9983226f5b6
|
Provenance
The following attestation bundles were made for juniper_service_core-0.4.0-py3-none-any.whl:
Publisher:
publish-service-core.yml on pcalnon/juniper-ml
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
juniper_service_core-0.4.0-py3-none-any.whl -
Subject digest:
8549dd20261c8806643b1f260aa95b9e8a090f02be15ed19534a7bbef9d58ee5 - Sigstore transparency entry: 2042362776
- Sigstore integration time:
-
Permalink:
pcalnon/juniper-ml@e5d9617a6d44e2dfbd93fa29d71f13419ea25eaa -
Branch / Tag:
refs/tags/juniper-service-core-v0.4.0 - Owner: https://github.com/pcalnon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-service-core.yml@e5d9617a6d44e2dfbd93fa29d71f13419ea25eaa -
Trigger Event:
release
-
Statement type: