odmantic icon indicating copy to clipboard operation
odmantic copied to clipboard

Embedded models have to be defined before any normal models that reference them

Open duhby opened this issue 1 year ago • 4 comments

Bug

No matter how you type hint a field in the base model that references an embedded one (including future annotations), defining indexes for fields that are in the embedded model, or trying to query/sort by fields that are in the embedded model throws an error when the embedded model is defined after the base model, but not when it is defined before.

Current Behavior

from odmantic import AIOEngine, EmbeddedModel, Index, Model
from odmantic.query import asc


class Bar(Model):
    bar: "Foo"

    model_config = {
        "indexes": lambda: [Index(asc(Bar.bar.foo))]
    }


class Foo(EmbeddedModel):
    foo: int


engine = AIOEngine()
await engine.configure_database([Bar])  # throws error
await engine.find(Bar, sort=asc(Bar.bar.foo))  # also throws error

The error that gets thrown both times is: AttributeError: operator foo not allowed for ODMField fields

However, when you switch the model definitions around like this:

...

class Foo(EmbeddedModel):
    foo: int


class Bar(Model):
    bar: "Foo"

    model_config = {
        "indexes": lambda: [Index(asc(Bar.bar.foo))]
    }

...

it behaves as expected (see "Expected behavior").

Expected behavior

No errors are thrown and the engine methods work as intended.

If it's intentional or expected behavior, it should be clear in the documentation.

Environment

  • ODMantic version: 1.0.0
  • MongoDB version: ...
  • Pydantic version: 2.5.3
  • Pydantic-core version: 2.14.6
  • Python version: 3.12.0

Additional context

It doesn't matter if you use foo: int = Field(...) or just have it as foo: int. The behavior is unchanged. It doesn't matter if you use bar: "Foo", bar: Foo, or bar: Foo # (with future annotations). The behavior is unchanged. This is also the case for reference fields.

duhby avatar Jan 05 '24 07:01 duhby

But, that makes sense? :O The embedded model should be defined first so a definition exists?

Also: class Bar(Model): bar: "Foo" <------ should lose the quotation marks?

aaaand [Index(asc(Bar.bar.foo)] <--- missing a parentheses :D : [Index(asc(Bar.bar.foo))]

rollebolle123 avatar Jan 08 '24 17:01 rollebolle123

Well as I mentioned, it doesn't matter if the quotations are there or not for both cases, and I didn't test the example code specifically so I missed the parentheses. I just think it should be specified in the documentation because it's not obvious that it should be defined before (other odm libraries don't have this behavior).

When you say "the embedded model should be defined first so a definition exists," that should (intuitively) only be necessary for the type hint if you want to define it without quotations. The reason I say it's not intuitive or obvious is because by the time any code is run regarding creating indexes or creating search queries, both of the models have already been defined.

I'm not saying the behavior is bad, it just needs to be clarified somewhere, and it might be unintended.

duhby avatar Jan 08 '24 17:01 duhby

Also, what's with the unnecessary condescension? You clearly didn't take the time to fully understand the original post.

duhby avatar Jan 08 '24 18:01 duhby

No, sorry, I am not trying to be condescending. And maybe I did not understand the original problem. Aaaaand, I had no idea you could put quotes around types to forward declare them. Then it makes sense to me :D

rollebolle123 avatar Jan 08 '24 18:01 rollebolle123