Skip to main content

juniper-service-core

PyPI Python 3.12+ License: MIT

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_2026-05-31_JUNIPER-ECOSYSTEM_MODEL-MIDDLEWARE-REFACTOR-DESIGN-AND-PLAN.md in juniper-ml).

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

juniper_service_core-0.6.0.tar.gz (196.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

juniper_service_core-0.6.0-py3-none-any.whl (133.9 kB view details)

Uploaded Python 3

File details

Details for the file juniper_service_core-0.6.0.tar.gz.

File metadata

  • Download URL: juniper_service_core-0.6.0.tar.gz
  • Upload date:
  • Size: 196.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for juniper_service_core-0.6.0.tar.gz
Algorithm Hash digest
SHA256 88b4685c8aa0b4527d49caec8ee50c8c65e3900a47c639c730a2717166dd6dde
MD5 46e448ba52d68f5917aebf2140956803
BLAKE2b-256 2f13be66e5731cf6c60516855e0725a20c07b11a35fe339676ae6867f10bfe49

See more details on using hashes here.

Provenance

The following attestation bundles were made for juniper_service_core-0.6.0.tar.gz:

Publisher: publish-service-core.yml on pcalnon/juniper-ml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file juniper_service_core-0.6.0-py3-none-any.whl.

File metadata

File hashes

Hashes for juniper_service_core-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 275547935f10b00e4f4dd0d06c9b34cc7d7af60c0d62e2a1f82ee321546cc1e4
MD5 a02233ae51f389c7673584da9af66ad0
BLAKE2b-256 aa80e734968c00b0d3dcbdcb5228e886be94093dc0e0862bfca0669af6224340

See more details on using hashes here.

Provenance

The following attestation bundles were made for juniper_service_core-0.6.0-py3-none-any.whl:

Publisher: publish-service-core.yml on pcalnon/juniper-ml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.7.0

2 files

This release

0.6.0 This release

2 files

0.5.1

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page