Skip to content

Document that reserved names are also reserved document keys on extra="allow" models #153

Open

Description

@cdwilson

What happens

On an extra="allow" model, pydantic reaches extra values through instance __getattr__, which fires only when normal attribute lookup fails. A stored key that collides with a model method therefore loads without complaint but is unreachable as an attribute — lookup finds the method first, and nothing warns. The value is still reachable — doc.model_extra["count"] for access, tinydb.where("count") for queries — but nothing points the user there.

No definition-time check can catch this: the collision depends on what keys are in the database at run time, not on what the class declares. It is the runtime blind spot of ShadowedFieldError, and it is inherent to Python attribute lookup — the ask here is documentation, not a behavior change.

Why it matters

extra="allow" is precisely the configuration used over legacy or foreign TinyDB data — docs/usage/schema-evolution.md shows it for gradual migration, and docs/usage/queries.md points raw where() queries at "extra="allow" documents, legacy keys" — which is the data most likely to contain keys like count, search, or json. And doc.count returning a bound method can even look plausible in passing. The docs never mention the collision or model_extra at all: grep -rin "model_extra" docs/ README.md returns nothing (run 2026-08-14 at 389c6d9).

What to do

Wherever the reserved-name material planned in #121 lands, add: on extra="allow" models the reserved names apply to document keys as well; a collided extra key is read via doc.model_extra[...] and queried via tinydb.where(...). The pages that discuss extra="allow" (models, queries, schema-evolution, security) can share one cross-reference to it.

A code-level courtesy — from_tinydb_document() warning when an extra key shadows a class attribute — would be an enhancement with a per-read cost, worth weighing separately if this bites in practice; it is not part of this ask.

Reproduction

from pydantic import ConfigDict
from tinydb import TinyDB
from tinydb.storages import MemoryStorage

from tinydantic import TinydanticModel

db = TinyDB(storage=MemoryStorage)


class Doc(TinydanticModel, database=db):
    model_config = ConfigDict(extra="allow")

    name: str


Doc.get_table().insert({"name": "a", "count": 42})
doc = Doc.all()[0]
print("doc.count:", doc.count)
print("doc.model_extra:", doc.model_extra)

Run with python repro.py (project venv).

Actual output:

doc.count: <bound method TinydanticModel.count of <class '__main__.Doc'>>
doc.model_extra: {'count': 42}

Expected: the same output — the behavior is inherent and not itself the defect. What is missing is documentation stating it; the docs-absence evidence is the grep above.

Environment: tinydantic 0.4.0 at 389c6d9, Python 3.10.20, TinyDB 4.9.0, pydantic 2.13.4, macOS. Run 2026-08-14.


The reproduction was run as shown. No docs change was drafted. Surfaced while working through the conflict-handling story for #121.

Assisted-by: Claude:claude-fable-5

Activity

  1. added
    documentationImprovements or additions to documentation
    ai-assistedCreated or investigated with help from an AI agent
    on Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ai-assistedCreated or investigated with help from an AI agentdocumentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions