Skip to content

Commit

Permalink
Merge pull request #268 from mesozoic/runtime_orm_meta
Browse files Browse the repository at this point in the history
Allow ORM Meta options to be lookups at runtime (not import time)
  • Loading branch information
mesozoic authored Jul 31, 2023
2 parents c08963e + dd46375 commit a3b471a
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 2 deletions.
2 changes: 2 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ See :ref:`Migrating from 1.x to 2.0` for detailed migration notes.
- `PR #273 <https://github.com/gtalarico/pyairtable/pull/273>`_.
* pyAirtable will automatically retry requests when throttled by Airtable's QPS.
- `PR #272 <https://github.com/gtalarico/pyairtable/pull/272>`_.
* ORM Meta attributes can now be defined as callables.
- `PR #268 <https://github.com/gtalarico/pyairtable/pull/268>`_.
* Removed ``ApiAbstract``.
- `PR #267 <https://github.com/gtalarico/pyairtable/pull/267>`_.
* Implemented strict type annotations on all functions and methods.
Expand Down
22 changes: 20 additions & 2 deletions pyairtable/orm/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@

class Model:
"""
This class allows you create an orm-style class for your Airtable tables.
This class allows you create an ORM-style class for your Airtable tables.
This is a meta class and can only be used to define sub-classes.
This is a metaclass and can only be used to define sub-classes.
The ``Meta`` is reuired and must specify all three attributes: ``base_id``,
``table_id``, and ``api_key``.
Expand All @@ -38,6 +38,22 @@ class Model:
... api_key = "keyapikey"
... timeout: Optional[Tuple[int, int]] = (5, 5)
... typecast: bool = True
You can implement meta attributes as callables if certain values
need to be dynamically provided or are unavailable at import time:
>>> from pyairtable.orm import Model, fields
>>> class Contact(Model):
... first_name = fields.TextField("First Name")
... age = fields.IntegerField("Age")
...
... class Meta:
... base_id = "appaPqizdsNHDvlEm"
... table_name = "Contact"
...
... @staticmethod
... def api_key():
... return os.environ["AIRTABLE_API_KEY"]
"""

id: str = ""
Expand Down Expand Up @@ -125,6 +141,8 @@ def _get_meta(cls, name: str, default: Any = None, required: bool = False) -> An
if required and not hasattr(cls.Meta, name):
raise ValueError(f"{cls.__name__}.Meta.{name} must be defined")
value = getattr(cls.Meta, name, default)
if callable(value):
value = value()
if required and value is None:
raise ValueError(f"{cls.__name__}.Meta.{name} cannot be None")
return value
Expand Down
27 changes: 27 additions & 0 deletions tests/test_orm_model.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from functools import partial
from unittest import mock

import pytest
Expand Down Expand Up @@ -118,3 +119,29 @@ def test_from_ids(mock_all):
with pytest.raises(KeyError):
FakeModel.from_ids(fake_ids + ["recDefinitelyNotValid"])
mock_all.assert_called_once()


def test_dynamic_model_meta():
"""
Test that we can provide callables in our Meta class to provide
the access token, base ID, and table name at runtime.
"""
data = {
"api_key": "FakeApiKey",
"base_id": "appFakeBaseId",
"table_name": "tblFakeTableName",
}

class Fake(Model):
class Meta:
api_key = lambda: data["api_key"] # noqa
base_id = partial(data.get, "base_id")

@staticmethod
def table_name():
return data["table_name"]

f = Fake()
assert f._get_meta("api_key") == data["api_key"]
assert f._get_meta("base_id") == data["base_id"]
assert f._get_meta("table_name") == data["table_name"]

0 comments on commit a3b471a

Please sign in to comment.