diff --git a/pyairtable/orm/model.py b/pyairtable/orm/model.py index 3d6300c5..3bf16116 100644 --- a/pyairtable/orm/model.py +++ b/pyairtable/orm/model.py @@ -499,8 +499,7 @@ class _Meta: @property def _config(self) -> Mapping[str, Any]: - if not (model_meta := getattr(self.model, "Meta", None)): - raise AttributeError(f"{self.model.__name__}.Meta must be defined") + model_meta = self.model.Meta # type: ignore[attr-defined] if isinstance(model_meta, dict): return model_meta if isinstance(model_meta, type): diff --git a/pyairtable/testing.py b/pyairtable/testing.py index fae3f702..c474b9f6 100644 --- a/pyairtable/testing.py +++ b/pyairtable/testing.py @@ -5,8 +5,10 @@ import datetime import random import string -from typing import Any, Optional +from typing import Any, Optional, Union +from pyairtable.api import retrying +from pyairtable.api.api import TimeoutTuple from pyairtable.api.types import AttachmentDict, CollaboratorDict, Fields, RecordDict from pyairtable.utils import is_airtable_id @@ -35,6 +37,8 @@ def fake_meta( base_id: str = "", table_name: str = "", api_key: str = "patFakePersonalAccessToken", + timeout: Optional[TimeoutTuple] = None, + retry: Optional[Union[bool, retrying.Retry]] = None, typecast: bool = True, use_field_ids: bool = False, memoize: bool = False, @@ -47,6 +51,8 @@ def fake_meta( "table_name": table_name or fake_id("tbl"), "api_key": api_key, "typecast": typecast, + "timeout": timeout, + "retry": retry, "use_field_ids": use_field_ids, "memoize": memoize, } diff --git a/tests/test_orm_model.py b/tests/test_orm_model.py index 18fe7299..ce9b40cb 100644 --- a/tests/test_orm_model.py +++ b/tests/test_orm_model.py @@ -68,6 +68,17 @@ class Address(Model): m.assert_not_called() +@pytest.mark.parametrize("invalid_meta", (None, 123, "asdf", [])) +def test_model_meta_type_error(invalid_meta): + """ + Test that Meta cannot be other wild stuff. + """ + with pytest.raises(TypeError): + + class Dummy(Model): + Meta = invalid_meta + + @pytest.mark.parametrize("name", ("exists", "id")) def test_model_overlapping(name): """ @@ -271,15 +282,44 @@ def test_meta_wrapper(): """ Test that Model subclasses have access to the _Meta wrapper. """ - original_meta = fake_meta(api_key="asdf") class Dummy(Model): - Meta = original_meta + Meta = fake_meta(api_key="asdf") assert Dummy.meta.model is Dummy assert Dummy.meta.api.api_key == "asdf" +def test_meta_dict(): + """ + Test that Meta can be a dict instead of a class. + """ + + class Dummy(Model): + Meta = { + "api_key": "asdf", + "base_id": "qwer", + "table_name": "zxcv", + "timeout": (1, 1), + } + + assert Dummy.meta.model is Dummy + assert Dummy.meta.api.api_key == "asdf" + + +@pytest.mark.parametrize("meta_kwargs", [{"timeout": 1}, {"retry": "asdf"}]) +def test_meta_type_check(meta_kwargs): + """ + Test that we check types on certain Meta attributes. + """ + + class Dummy(Model): + Meta = fake_meta(**meta_kwargs) + + with pytest.raises(TypeError): + Dummy.meta.api + + def test_dynamic_model_meta(): """ Test that we can provide callables in our Meta class to provide