Skip to content

Accept DatabaseConfig values by field name, not only by env alias - #37

Merged
eldonm merged 1 commit into
mainfrom
api/database-config-populate-by-name
Aug 5, 2026
Merged

Accept DatabaseConfig values by field name, not only by env alias#37
eldonm merged 1 commit into
mainfrom
api/database-config-populate-by-name

Conversation

@eldonm

@eldonm eldonm commented Aug 5, 2026

Copy link
Copy Markdown
Member

Type of Change

  • 🐛 Bug Fix
  • 🚀 Feature Request
  • 🔄 Refactor
  • 📖 Documentation Update

Summary

What does this PR address?

DatabaseConfig silently discarded every value passed by field name:

DatabaseConfig(dynamodb_table_name="t").dynamodb_table_name   # -> None
DatabaseConfig(**{"JVSPATIAL_DYNAMODB_TABLE_NAME": "t"})      # -> "t"

Its aliased fields carry a validation_alias, and a pydantic v2 model accepts an aliased field only by that alias unless it opts into populate_by_name. AuthConfig opts in (config_groups.py:138); DatabaseConfig never did.


Description

Bug Fixes:

The bug. Two paths pass these fields by name, and both were inert:

  1. server_config_overrides_from_env() builds its database group keyed by field name (db["dynamodb_table_name"] = ..., and since 0.0.16 db["postgres_dsn"] = ...). None of it reached the model.
  2. Embedding hosts constructing a DatabaseConfig directly. jvagent passes dynamodb_table_name / dynamodb_region / dynamodb_endpoint_url this way — so an app.yaml table name or region has never actually taken effect for DynamoDB.

Why it went unnoticed. Nothing raised. The adapters read the same settings from env themselves, so deployments configured purely through env vars worked exactly as expected — the configuration object just wasn't the source of truth it appeared to be. It only becomes visible when someone configures via app.yaml, or asserts on the model.

Scope. DynamoDB has been affected since those fields were added. Postgres inherited it in 0.0.16.

The fix. The single line AuthConfig already carries:

model_config = ConfigDict(populate_by_name=True)

Alias population is unchanged — both spellings now work, which is what the env-override path needs.


Changes Made

  1. jvspatial/api/config_groups.pypopulate_by_name on DatabaseConfig, with a comment explaining why the aliased fields need it.
  2. tests/api/test_database_config_population.pynew. Both population paths for all 9 aliased fields (dynamodb + postgres), integer fields, unaliased fields unaffected, and one test driving server_config_overrides_from_env() output onto the model — the path that was inert.
  3. CHANGELOG.md — entry under [Unreleased].

Checklist

  • Code follows the project's coding guidelines.
  • Tests have been added for the fixed behavior — 18 cases, confirmed red before the fix (DatabaseConfig(...) returning None for every aliased field).
  • Documentation updated — changelog; no doc page describes this behavior.
  • Existing tests pass locally — targeted suites (test_server_config, test_database_configurator, test_env_adapter_postgres, test_server_env_integration, plus the new file) all green. Full-suite run was still going when this was opened; CI is authoritative and re-runs it.
  • Any dependencies introduced are justified and documented — none.

Steps to Test

pytest tests/api/test_database_config_population.py -q

Against main these fail with assert None == 'my-table' for each aliased field. The end-to-end shape:

from jvspatial.api.config_groups import DatabaseConfig
DatabaseConfig(postgres_dsn="postgresql://u:p@h:5432/db").postgres_dsn

Additional Context

Found while wiring jvagent's Postgres settings through ServerConfig — the passthrough was written correctly and did nothing, which is what led back here.

This also exposes a gap in my own #35 tests. test_postgres_env_maps_into_database_group asserts the dict server_config_overrides_from_env() produces, never that the dict lands on the model. It passed happily while the path was dead. The new test_env_adapter_output_lands_on_the_model closes that.

Worth considering as a follow-up: the same validation_alias-without-populate_by_name shape would be an easy trap in any future config group. A test that walks every group in config_groups.py, finds fields with a validation_alias, and asserts field-name population would prevent a repeat — happy to add it here or separately.


Questions or Concerns

None on the fix itself. One judgment call worth confirming: this makes app.yaml-supplied DynamoDB settings actually take effect for the first time. For a deployment that has both an app.yaml table name and a JVSPATIAL_DYNAMODB_TABLE_NAME env var pointing at different tables, behavior changes — env previously won by default. That's the documented precedence restored, but if you'd rather it ship in a minor rather than a patch, say so.

Its aliased fields carry a validation_alias, and a pydantic v2 model
accepts an aliased field only by that alias unless it opts into
populate_by_name. AuthConfig does; DatabaseConfig did not. So every value
passed by field name was silently discarded:

    DatabaseConfig(dynamodb_table_name="t").dynamodb_table_name  -> None
    DatabaseConfig(**{"JVSPATIAL_DYNAMODB_TABLE_NAME": "t"})      -> "t"

Two paths were affected. server_config_overrides_from_env() keys its
database group by field name, so its dynamodb and postgres mappings never
reached the model. And embedding hosts that build a DatabaseConfig
directly -- jvagent passes dynamodb_table_name / dynamodb_region this way
-- were likewise ignored, which means an app.yaml table name or region has
never taken effect for DynamoDB.

Nothing raised, because the adapters read the same settings from env
themselves and carried on. The configuration object simply was not the
source of truth it appeared to be. DynamoDB has been affected since those
fields were added; postgres inherited it in 0.0.16.

Fix is the one line AuthConfig already carries. Tests cover both
population paths for all aliased fields and drive
server_config_overrides_from_env output onto the model -- the path that
was inert, and that my earlier postgres tests missed by asserting the
dict rather than the model built from it.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Benchmark comparison

Threshold: ±25% (informational, does not block merge)

benchmark baseline (s) current (s) delta status
tests/benchmarks/test_deferred_save_benchmarks.py::test_bench_deferred_save_batched_100 0.040001 0.039424 -1.4% OK
tests/benchmarks/test_deferred_save_benchmarks.py::test_bench_immediate_save_100 0.038061 0.038949 +2.3% OK
tests/benchmarks/test_jsondb_benchmarks.py::test_bench_jsondb_batched_saves_500 0.405021 0.402758 -0.6% OK
tests/benchmarks/test_jsondb_benchmarks.py::test_bench_jsondb_count_empty_query 0.861254 0.947287 +10.0% OK
tests/benchmarks/test_jsondb_benchmarks.py::test_bench_jsondb_count_filtered 1.007568 1.009064 +0.1% OK
tests/benchmarks/test_jsondb_benchmarks.py::test_bench_jsondb_find_filtered 0.765896 0.781538 +2.0% OK
tests/benchmarks/test_jsondb_benchmarks.py::test_bench_jsondb_save_throughput 0.001528 0.001561 +2.1% OK
tests/benchmarks/test_sqlite_benchmarks.py::test_bench_sqlite_count_empty 0.237247 0.261848 +10.4% OK
tests/benchmarks/test_sqlite_benchmarks.py::test_bench_sqlite_count_fallback_via_regex 0.286885 0.307295 +7.1% OK
tests/benchmarks/test_sqlite_benchmarks.py::test_bench_sqlite_count_pushdown 0.256302 0.258535 +0.9% OK
tests/benchmarks/test_sqlite_benchmarks.py::test_bench_sqlite_find_fallback_via_regex 0.271339 0.302198 +11.4% OK
tests/benchmarks/test_sqlite_benchmarks.py::test_bench_sqlite_find_pushdown 0.247682 0.243125 -1.8% OK
tests/benchmarks/test_sqlite_benchmarks.py::test_bench_sqlite_sort_limit_pushdown 0.283333 0.278520 -1.7% OK

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant