Repository navigation
Conversation
added 5 commits
September 18, 2026 11:10
RestEndpointMeta._process did seven numbered steps in one function: collect annotations, build columns, inject the auto columns, build schemas, parse Meta, scan the MRO for HTTP method mixins, pick the table source. Reading how one field becomes one column meant scrolling past the other six. Each step is a function now: _field_annotations, _declared_columns, _column_for, _auto_columns, _parsed_meta, _allowed_methods and _reject_redeclared_auto_fields. _process is the 20 lines that put them in order, and a reflected endpoint returns early instead of threading `if reflect` through every step. Three things that did nothing are gone: - cls._fields_info was stored on every endpoint and never read. - The block that removed "fields inherited from RestEndpoint itself" could not match anything: RestEndpoint only has underscore annotations, which the line above already skips. - ReflectedTable's partial_columns was always an empty list, because columns are only built when Meta.reflect is falsy. Partial reflection maps columns that already exist in the database; it never added any. Tests: 345 passed, 1 xfailed, 2 xpassed.
register() was 270 lines when this work started. After the table sources and the auth change it still validated the mapping, detected async mode, checked the engine, mapped tables, built routes and rebuilt the login routes, all inline. Now it reads as the four steps it performs. The checks are functions (_require_rest_endpoint, _has_async_handlers, _require_async_engine) and one endpoint's routes are built in _register_endpoint. The constructor delegates to _engine_from_url, _checked_mode and _as_rate_limiter. run() built its own Starlette app, a copy of build_app() with debug added. It calls build_app(debug=debug) now; debug is a new keyword-only argument with the old default. The login routes were torn out of self._routes and re-inserted on every register() call. They are composed in _asgi_routes() when the app is built: login routes, endpoint routes, /healthz, in the same order as before. The "queryset is async but engine is sync" warning is removed. It could not fire: an async queryset switches the app to async mode in the first pass, before the second pass that checked for it. lightapi.py: 910 lines on dev, 395 now. Tests: 345 passed, 1 xfailed, 2 xpassed.
_update was 100 lines that validated input, built a pydantic model, inspected the mapper, ran three SQL statements and chose between four HTTP responses. _retrieve, _create and _destroy mixed SQL and HTTP the same way. Repository(model, session) now owns storage: get, add, update, delete, the created_at/updated_at timestamps and the optimistic lock on `version`. It knows nothing about HTTP and reports problems as RowNotFound and VersionConflict. RestEndpoint validates, calls it, and turns those two errors into 404 and 409 through one table, so the status and wording of each error exist once. The PATCH schema (every field optional) was rebuilt with pydantic.create_model on every PATCH request. patch_schema() builds it once per create schema and caches it. Public CRUD methods and the _*_async helpers are untouched; they still call _retrieve, _create, _update and _destroy. tests/test_repository.py covers the lock directly: a stale version raises and leaves the row alone, a current one bumps the version. It fails if the version check or the cache is removed. rest.py: 819 lines on dev, 505 now. Tests: 350 passed, 1 xfailed, 2 xpassed.
yaml_loader.py was 705 lines doing three jobs: resolving class names, describing the YAML document as pydantic models, and turning a validated document into endpoint classes. - yaml_names.py: the built-in name registry and dotted-path resolution. - yaml_schema.py: the pydantic models and the field type map. - yaml_loader.py: the translation and load_config (358 lines). The rule "endpoint settings win, defaults fill the gaps" was written out field by field twice, once in _make_authentication and again inside the per-method branch of _build_meta_class. AuthConfig.merged_over(defaults) states it once, on the object that owns the fields. _build_meta_class was 123 lines; its per-method permission logic is now _per_method_authentication and _permission_name. The branch that handled a default permission that was neither str nor dict is gone: the pydantic model only allows those two. The one test change is an import: _resolve_name is resolve_name in yaml_names. The 491-line test_yaml_config.py is otherwise untouched. Tests: 350 passed, 1 xfailed, 2 xpassed.
SchemaFactory.build and build_from_reflected_table ended with the same twenty lines: add the four auto-managed read fields, create the two pydantic models, rebuild them. build also imported Optional, datetime and ConfigDict a second time inside the function, and set read_fields identically in both arms of an if/else. _schema_pair does that tail once. Field order is unchanged (the endpoint's fields first, then any auto field not already present), and that order is the key order of every JSON response. build: 73 lines to 40. Tests unchanged: 350 passed, 1 xfailed, 2 xpassed.
This was referenced Sep 18, 2026
Owner
Author
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.
Stacked on #40 (which sits on #39 and #38). Merge in order.
What
The functions that did everything are split so each one has a single job. Five commits, no behaviour change:
RestEndpointMeta._processwas 150 lines with seven numbered steps. Each step is a function now (_field_annotations,_declared_columns,_column_for,_auto_columns,_parsed_meta,_allowed_methods), and_processis the 20 lines that order them.LightApi.register()was 270 lines ondev. It now validates, detects async mode, checks the engine and registers each endpoint, with each check in its own function.run()was a copy ofbuild_app()plusdebug; it callsbuild_app(debug=debug)now. The login routes are composed when the app is built instead of being removed and re-inserted on everyregister()call.lightapi.py: 910 lines ondev, 395 now.Repository._updatewas 100 lines that validated input, built a pydantic model, inspected the mapper, ran three SQL statements and picked between four HTTP responses.Repository(model, session)owns storage:get,add,update,delete, the timestamps and the optimistic lock onversion. It raisesRowNotFoundandVersionConflict;RestEndpointmaps those to 404 and 409 through one table.rest.py: 819 lines ondev, 505 now.yaml_names.py(name resolution),yaml_schema.py(the pydantic models) andyaml_loader.py(the translation, 358 lines). The rule "endpoint settings win, defaults fill the gaps" was written out field by field twice;AuthConfig.merged_over(defaults)states it once.SchemaFactory.buildandbuild_from_reflected_tableended with the same twenty lines._schema_pairdoes that once. Field order, which is the key order of every JSON response, is unchanged.Also: the PATCH schema was rebuilt with
pydantic.create_modelon every PATCH request.patch_schema()builds it once per endpoint and caches it.Removed because it did nothing
cls._fields_info: stored on every endpoint, never read.RestEndpointonly has underscore annotations, which are skipped one line earlier.ReflectedTable'spartial_columns: always an empty list, because columns are only built whenMeta.reflectis falsy.What I left alone, and why
_metastays adict. The plan was a typed dataclass, but the README and the REST reference both document_metaasdict, and a custom filter backend receives the view and can readview._meta.get(...).Checks
All run on my machine. The test workflow does not run for PRs into
dev.pytest: 375 passed, 1 xfailed, 2 xpassed. That is 370 from refactor: replace type checks with polymorphism (handler, auth, pagination, table mapping) #40 (which includes the 25 tests of the reflected-type fix, merged up from there) plus 5 new ones intests/test_repository.py.Repository.updateor the cache frompatch_schema._resolve_nameisresolve_nameinyaml_names. The 491-linetest_yaml_config.pyand the 1,002-linetest_login_auth.pyare otherwise untouched.lightapi/: 85% before, 86% now (2,090 statements, 296 missed).ruff check,ruff format --check,mypy: clean. The new modules are type-checked; they are not in theignore_errorslist.Largest functions left:
load_from_dict(137 lines, not touched because it is planned for removal),_build_endpoint_class(66),load_config(55).