Package LightAPI as a Helm chart for zero-CRUD-code deploys - #33
Merged
Conversation
End-user flow:
docker run --rm -p 8000:8000 \
-v ./lightapi.yaml:/app/lightapi.yaml:ro \
-e DATABASE_URL=sqlite:////app/data.db \
iklobato/lightapi:latest
What ships:
- Dockerfile (python:3.12-slim base, multi-arch friendly).
Pre-installs lightapi[async] + psycopg2-binary so both sync PostgreSQL
and async asyncpg/aiosqlite engines work out of the box. Drops privileges
to a non-root `lightapi` user. Build arg LIGHTAPI_VERSION pins the wheel
the workflow installs (kept in sync with pyproject.toml at publish time).
- docker/entrypoint.py: 60-line launcher that locates the YAML at
/app/lightapi.yaml (overridable via LIGHTAPI_CONFIG), runs
LightApi.from_config() + build_app(), and starts uvicorn with
proxy_headers=True so common reverse-proxy headers are respected.
- docker/lightapi.example.yaml: minimal config used as a smoke-test fixture.
- .dockerignore: excludes .git, .venv, tests, docs, examples, site, etc.
- .github/workflows/docker-publish.yml: builds linux/amd64 + linux/arm64
on master pushes and v*.*.* tag pushes, pushes to iklobato/lightapi
using DOCKERHUB_USERNAME + DOCKERHUB_TOKEN repo secrets. Tag rules:
v0.1.21 tag → :0.1.21, :0.1, :latest
master push → :master
workflow_dispatch → :manual-<run-number>
Docs:
- README: new Docker quickstart block.
- docs/deployment/docker.md: complete rewrite covering quickstart, env
vars, SQLite-with-volume, docker compose + PostgreSQL, Redis cache,
JWT auth with a mounted login_validator module, custom image
extension pattern, and the publish-pipeline secrets.
Verified locally with docker build + docker run; POST /books, GET
/books, POST /authors, GET /authors all return the expected responses
against the bundled example config.
feat(docker): publish ready-to-use iklobato/lightapi image
The DOCKERHUB_TOKEN authenticates against the Docker Hub account `iklob1` (verified by a successful `docker login -u iklob1`), not the GitHub-username `iklobato`. Updated the image references in: - .github/workflows/docker-publish.yml — image and job name - README.md — Docker quickstart pull line - docker/lightapi.example.yaml — header comment - docs/deployment/docker.md — every image reference + secret table `github.com/iklobato/...` URLs are unchanged (still the GitHub repo).
fix(docker): publish under iklob1/lightapi instead of iklobato/lightapi
…ict fields Bug fixes - login_validator exceptions now return 401 instead of 500 - SearchFilter escapes % and _ so search terms are treated as literals - OrderingFilter with empty whitelist now disables ordering entirely - PATCH null clears Optional/nullable fields (was silently ignored) - from_dict fields were silently dropped (missing __annotations__) - from_dict methods list now enforces HTTP verbs via HttpMethod mixins Tests - Update test_login_validator_exception_returns_500 → asserts 401 Documentation - Complete LightApi() constructor signature in README - Correct rate_limiter dict keys and scope (auth/login only) - Document SearchFilter literal matching and OrderingFilter whitelist - Document PATCH null-clearing semantics for Optional fields - Document login_validator exception → 401 behavior - Add CHANGELOG.md
Bugs fixed:
- yaml_loader: Ellipsis class attributes blocked SQLAlchemy instrumentation.
Fields with no constraints (no Field() in class_attrs) were stored as
Ellipsis, which SA's map_imperatively silently skips — the column existed
in _all_columns but never appeared in the mapper, causing NOT NULL
constraint failures on INSERT.
Fixed in FieldInfoStripper.strip() (table_mapping.py): also remove Ellipsis
attributes so SA can install InstrumentedAttribute freely.
- yaml_loader: YAML 'default' value was silently dropped.
'default' is not in constraint_keys, so filtering pydantic_kwargs discarded
it. pydantic_kwargs.pop("default", ...) then returned the sentinel,
making every YAML-defined default invisible to Pydantic.
Fixed: extract default from the raw extra dict before filtering.
- lightapi.py build_app(): CORS middleware was only applied in run(), not
build_app(). TestClient and embedded-app users never got CORS headers.
Fixed: apply StarletteCORSMiddleware in build_app() too.
- auth_checker.py: AllowAny permission (non-dict) did not bypass the backend
authentication check. An endpoint with permission=AllowAny but a backend
from defaults still required a valid token.
Fixed: return None before the backend check when permission_cls is AllowAny
and is not a per-method dict. permission_cls=None (unset) still triggers
auth when a backend is configured.
- yaml_loader: dict-style methods ({GET: ..., POST: ...}) did not restrict
HTTP verbs — _resolve_methods_bases returned (RestEndpoint,) for any dict,
allowing all five methods.
Fixed: extract dict keys as the method list and resolve HttpMethod mixins.
- yaml_loader: MetaConfig had no 'table' field, so meta.table in YAML was
silently ignored. Reflected endpoints could not name their table.
Fixed: add table field to MetaConfig and pass it through _build_meta_class.
- meta.cache: { ttl: N } — maps to Meta.cache = Cache(ttl=N); endpoint
GET responses are cached in Redis when available, silently skipped if not.
- meta.serializer: { fields: [...] } or { read: [...], write: [...] } —
maps to Meta.serializer = Serializer(...); controls which fields appear
in responses, globally or per HTTP verb.
- Top-level mode: "sync" | "async" — passed to LightApi(mode=...) so async
engines can be declared entirely from YAML without a Python override.
All three features are wired through _build_meta_class and load_config.
- Add meta.cache, meta.serializer, mode, meta.table to YAML schema references in README, configuration guide, and YAML examples - Add full-featured YAML example (per-method auth + cache + serializer + cursor pagination + reflection) to configuration guide - Rewrite examples/yaml-configuration.md to cover every supported key - Fix incorrect 'cache is not auto-invalidated' claim in caching.md — writes DO invalidate the endpoint's cache prefix - Add YAML configuration sections to filtering.md and pagination.md - Fix single-arg Middleware.process() signature in configuration.md to the correct (request, response) two-phase form - Document AllowAny bypassing the auth backend, and login_validator exceptions mapping to 401 All 39 YAML blocks parse, all 41 Python blocks compile, 324 tests pass.
A declarative endpoint field with no constraints and no default (e.g.
`price: {type: float}`) was given an Ellipsis class attribute. Ellipsis is
not a FieldInfo, so FieldInfoStripper never removed it, and the leftover
class attribute stopped SQLAlchemy from mapping the column. The column was
created NOT NULL by create_all but its value was silently dropped on INSERT,
so every write to such a field raised a NOT NULL violation.
Leave these fields annotation-only: the annotation alone already makes the
generated schema field required, and the column now maps and persists.
Claude-Session: https://claude.ai/code/session_01LWV5jPTwsAehx2rM7UZjtq
Deploy a declarative CRUD API over an existing database with only a values.yaml: the chart hands LightApi.from_config the declarative config and runs it, so teams write no CRUD code. - ServerCommand (lightapi/server.py) + `lightapi serve` console script + `python -m lightapi`: boot a server from LIGHTAPI_CONFIG/HOST/PORT. - Always-on /healthz route (lightapi/health.py) for orchestrator probes, wired once into both run() and build_app(). - Generic Dockerfile: one image, config injected at deploy time. - charts/lightapi: Deployment (config via ConfigMap, secrets via Secret, /healthz liveness+readiness probes), Service, Secret, ServiceAccount, optional Ingress and HPA. Verified end to end on minikube: helm install/upgrade/rollback, config-change pod roll, secret and existingSecret paths, HPA, method restriction, optimistic locking, and broken-config CrashLoopBackOff with no traffic served. Claude-Session: https://claude.ai/code/session_01LWV5jPTwsAehx2rM7UZjtq
- README: env vars (LIGHTAPI_CONFIG/HOST/PORT), a `lightapi serve` note, a health-check note, and a Kubernetes (Helm) section. - docs/deployment/helm.md: full Helm guide with a minikube walkthrough. - charts/lightapi/README.md: chart usage and a complete values reference. - docs/deployment/docker.md: align with the real image (lightapi serve + LIGHTAPI_CONFIG) instead of the old gunicorn placeholder. - Add the Helm page to the deployment nav. Claude-Session: https://claude.ai/code/session_01LWV5jPTwsAehx2rM7UZjtq
Reconcile the two Docker packaging paths. Keep master's published iklob1/lightapi image, docker/entrypoint.py, and docker-publish workflow as the container story; keep this branch's Helm chart, /healthz route, and the YAML column-mapping fix on top. The chart now defaults to iklob1/lightapi. Conflicts resolved: - Dockerfile / .dockerignore / docs/deployment/docker.md: took master's. - lightapi/lightapi.py build_app(): kept master's CORS wiring and added the /healthz route via _asgi_routes(). The `lightapi serve` console command stays as a general library CLI alongside the container entrypoint. Claude-Session: https://claude.ai/code/session_01LWV5jPTwsAehx2rM7UZjtq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Ship LightAPI as a Helm chart so a team can stand up a full CRUD REST API over an existing database by writing only a values.yaml. No CRUD code, no per-project image build.
The chart hands the declarative config to
LightApi.from_configand runs it. Adding a table means adding an entry underconfig.endpointsand runninghelm upgrade.Changes
ServerCommand(lightapi/server.py) +lightapi serveconsole script +python -m lightapi: boot a server fromLIGHTAPI_CONFIG/LIGHTAPI_HOST/LIGHTAPI_PORT./healthzroute (lightapi/health.py) for liveness/readiness probes, wired once into bothrun()andbuild_app().Dockerfile+.dockerignore: one image, config injected at deploy time (includes psycopg2 for sync Postgres and the async extra).charts/lightapi: Deployment (config via ConfigMap, secrets via Secret,/healthzprobes), Service, Secret, ServiceAccount, optional Ingress and HPA.Bug fix included
A declarative field with no constraints and no default (e.g.
price: {type: float}) got an Ellipsis class attribute that was not stripped and stopped SQLAlchemy from mapping the column. The value was silently dropped on INSERT and any write hit a NOT NULL error. Fixed by leaving such fields annotation-only. Regression test added.Testing
Full suite: 327 passing. Verified end to end on minikube with Postgres:
existingSecretpathsNote
The probe is process level. If the database goes down but the process stays up,
/healthzstill returns 200. A readiness variant that pings the database is left as follow-up (noted in the plan doc).https://claude.ai/code/session_01LWV5jPTwsAehx2rM7UZjtq