Repository: gtalarico/pyairtable
Pull request #362 (2024-04-01 04:25:56)

Rename `return_fields_by_field_id=` to `use_field_ids=`
This is a breaking change for 3.0 and it's totally cosmetic and silly, but I find the name "return fields by field id" to be way too much of a mouthful. We called this `use_field_ids` in #355 and I'd like to rename the kwarg to match. When users upgrade the package it should be easy enough to search/replace everywhere. I am comfortable being told this is a bad idea anyway.


Patch:
diff --git a/docs/source/_substitutions.rst b/docs/source/_substitutions.rst
index 67309dd..1aea254 100644
--- a/docs/source/_substitutions.rst
+++ b/docs/source/_substitutions.rst
@@ -57,7 +57,7 @@
     by provided fields; if a field is not included its value will
     bet set to null. If ``False``, only provided fields are updated.
 
-.. |kwarg_return_fields_by_field_id| replace:: An optional boolean value that lets you return field objects where the
+.. |kwarg_use_field_ids| replace:: An optional boolean value that lets you return field objects where the
     key is the field id. This defaults to `false`, which returns field objects where the key is the field name.
 
 .. |kwarg_force_metadata| replace::
diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst
index 8022d4d..13d93b3 100644
--- a/docs/source/changelog.rst
+++ b/docs/source/changelog.rst
@@ -5,12 +5,24 @@ Changelog
 3.0 (TBD)
 ------------------------
 
+* Rewrite of :mod:`pyairtable.formulas` module.
+  - `PR #329 <https://github.com/gtalarico/pyairtable/pull/329>`_.
 * ORM fields :class:`~pyairtable.orm.fields.TextField` and
   :class:`~pyairtable.orm.fields.CheckboxField` will no longer
   return ``None`` when the field is empty.
   - `PR #347 <https://github.com/gtalarico/pyairtable/pull/347>`_.
-* Rewrite of :mod:`pyairtable.formulas` module.
-  - `PR #329 <https://github.com/gtalarico/pyairtable/pull/329>`_.
+* Changed the type of :data:`~pyairtable.orm.Model.created_time`
+  from ``str`` to ``datetime``, along with all other timestamp fields
+  used in :ref:`API: pyairtable.models`.
+  - `PR #352 <https://github.com/gtalarico/pyairtable/pull/352>`_.
+* Added ORM field type :class:`~pyairtable.orm.fields.SingleLinkField`
+  for record link fields that (generally) link to a single record.
+  - `PR #354 <https://github.com/gtalarico/pyairtable/pull/354>`_.
+* Renamed ``return_fields_by_field_id=`` to ``use_field_ids=``.
+* Support ``use_field_ids`` in the :ref:`ORM`.
+  - `PR #355 <https://github.com/gtalarico/pyairtable/pull/355>`_.
+* Removed the ``pyairtable.metadata`` module.
+  - `PR #360 <https://github.com/gtalarico/pyairtable/pull/360>`_.
 
 2.3.2 (2024-03-18)
 ------------------------
diff --git a/docs/source/tables.rst b/docs/source/tables.rst
index 552d7a4..84ec8cd 100644
--- a/docs/source/tables.rst
+++ b/docs/source/tables.rst
@@ -111,9 +111,8 @@ like :meth:`~pyairtable.Table.iterate` or :meth:`~pyairtable.Table.all`.
      - |kwarg_user_locale|
    * - ``time_zone``
      - |kwarg_time_zone|
-   * - ``return_fields_by_field_id``
-        .. versionadded:: 1.3.0
-     - |kwarg_return_fields_by_field_id|
+   * - ``use_field_ids``
+     - |kwarg_use_field_ids|
 
 
 Return Values
diff --git a/pyairtable/api/params.py b/pyairtable/api/params.py
index 82e2293..10a09e8 100644
--- a/pyairtable/api/params.py
+++ b/pyairtable/api/params.py
@@ -70,7 +70,7 @@ OPTIONS_TO_PARAMETERS = {
     "max_records": "maxRecords",
     "offset": "offset",
     "page_size": "pageSize",
-    "return_fields_by_field_id": "returnFieldsByFieldId",
+    "use_field_ids": "returnFieldsByFieldId",
     "sort": "sort",
     "time_zone": "timeZone",
     "user_locale": "userLocale",
diff --git a/pyairtable/api/table.py b/pyairtable/api/table.py
index 3050bbe..d885156 100644
--- a/pyairtable/api/table.py
+++ b/pyairtable/api/table.py
@@ -195,7 +195,7 @@ class Table:
             cell_format: |kwarg_cell_format|
             time_zone: |kwarg_time_zone|
             user_locale: |kwarg_user_locale|
-            return_fields_by_field_id: |kwarg_return_fields_by_field_id|
+            use_field_ids: |kwarg_use_field_ids|
         """
         record = self.api.get(self.record_url(record_id), options=options)
         return assert_typed_dict(RecordDict, record)
@@ -226,7 +226,7 @@ class Table:
             cell_format: |kwarg_cell_format|
             user_locale: |kwarg_user_locale|
             time_zone: |kwarg_time_zone|
-            return_fields_by_field_id: |kwarg_return_fields_by_field_id|
+            use_field_ids: |kwarg_use_field_ids|
         """
         if isinstance(formula := options.get("formula"), Formula):
             options["formula"] = to_formula_str(formula)
@@ -258,7 +258,7 @@ class Table:
             cell_format: |kwarg_cell_format|
             user_locale: |kwarg_user_locale|
             time_zone: |kwarg_time_zone|
-            return_fields_by_field_id: |kwarg_return_fields_by_field_id|
+            use_field_ids: |kwarg_use_field_ids|
         """
         return [record for page in self.iterate(**options) for record in page]
 
@@ -278,7 +278,7 @@ class Table:
             cell_format: |kwarg_cell_format|
             user_locale: |kwarg_user_locale|
             time_zone: |kwarg_time_zone|
-            return_fields_by_field_id: |kwarg_return_fields_by_field_id|
+            use_field_ids: |kwarg_use_field_ids|
         """
         options.update(dict(page_size=1, max_records=1))
         for page in self.iterate(**options):
@@ -290,7 +290,7 @@ class Table:
         self,
         fields: WritableFields,
         typecast: bool = False,
-        return_fields_by_field_id: bool = False,
+        use_field_ids: bool = False,
     ) -> RecordDict:
         """
         Create a new record
@@ -302,14 +302,14 @@ class Table:
         Args:
             fields: Fields to insert. Must be a dict with field names or IDs as keys.
             typecast: |kwarg_typecast|
-            return_fields_by_field_id: |kwarg_return_fields_by_field_id|
+            use_field_ids: |kwarg_use_field_ids|
         """
         created = self.api.post(
             url=self.url,
             json={
                 "fields": fields,
                 "typecast": typecast,
-                "returnFieldsByFieldId": return_fields_by_field_id,
+                "returnFieldsByFieldId": use_field_ids,
             },
         )
         return assert_typed_dict(RecordDict, created)
@@ -318,7 +318,7 @@ class Table:
         self,
         records: Iterable[WritableFields],
         typecast: bool = False,
-        return_fields_by_field_id: bool = False,
+        use_field_ids: bool = False,
     ) -> List[RecordDict]:
         """
         Create a number of new records in batches.
@@ -340,7 +340,7 @@ class Table:
         Args:
             records: Iterable of dicts representing records to be created.
             typecast: |kwarg_typecast|
-            return_fields_by_field_id: |kwarg_return_fields_by_field_id|
+            use_field_ids: |kwarg_use_field_ids|
         """
         inserted_records = []
 
@@ -354,7 +354,7 @@ class Table:
                 json={
                     "records": new_records,
                     "typecast": typecast,
-                    "returnFieldsByFieldId": return_fields_by_field_id,
+                    "returnFieldsByFieldId": use_field_ids,
                 },
             )
             inserted_records += assert_typed_dicts(RecordDict, response["records"])
@@ -367,7 +367,7 @@ class Table:
         fields: WritableFields,
         replace: bool = False,
         typecast: bool = False,
-        return_fields_by_field_id: bool = False,
+        use_field_ids: bool = False,
     ) -> RecordDict:
         """
         Update a particular record ID with the given fields.
@@ -382,7 +382,7 @@ class Table:
             fields: Fields to update. Must be a dict with column names or IDs as keys.
             replace: |kwarg_replace|
             typecast: |kwarg_typecast|
-            return_fields_by_field_id: |kwarg_return_fields_by_field_id|
+            use_field_ids: |kwarg_use_field_ids|
         """
         method = "put" if replace else "patch"
         updated = self.api.request(
@@ -391,7 +391,7 @@ class Table:
             json={
                 "fields": fields,
                 "typecast": typecast,
-                "returnFieldsByFieldId": return_fields_by_field_id,
+                "returnFieldsByFieldId": use_field_ids,
             },
         )
         return assert_typed_dict(RecordDict, updated)
@@ -401,7 +401,7 @@ class Table:
         records: Iterable[UpdateRecordDict],
         replace: bool = False,
         typecast: bool = False,
-        return_fields_by_field_id: bool = False,
+        use_field_ids: bool = False,
     ) -> List[RecordDict]:
         """
         Update several records in batches.
@@ -410,7 +410,7 @@ class Table:
             records: Records to update.
             replace: |kwarg_replace|
             typecast: |kwarg_typecast|
-            return_fields_by_field_id: |kwarg_return_fields_by_field_id|
+            use_field_ids: |kwarg_use_field_ids|
 
         Returns:
             The list of updated records.
@@ -429,7 +429,7 @@ class Table:
                 json={
                     "records": chunk_records,
                     "typecast": typecast,
-                    "returnFieldsByFieldId": return_fields_by_field_id,
+                    "returnFieldsByFieldId": use_field_ids,
                 },
             )
             updated_records += assert_typed_dicts(RecordDict, response["records"])
@@ -442,7 +442,7 @@ class Table:
         key_fields: List[FieldName],
         replace: bool = False,
         typecast: bool = False,
-        return_fields_by_field_id: bool = False,
+        use_field_ids: bool = False,
     ) -> UpsertResultDict:
         """
         Update or create records in batches, either using ``id`` (if given) or using a set of
@@ -457,7 +457,7 @@ class Table:
                 records in the input with existing records on the server.
             replace: |kwarg_replace|
             typecast: |kwarg_typecast|
-            return_fields_by_field_id: |kwarg_return_fields_by_field_id|
+            use_field_ids: |kwarg_use_field_ids|
 
         Returns:
             Lists of created/updated record IDs, along with the list of all records affected.
@@ -494,7 +494,7 @@ class Table:
                 json={
                     "records": formatted_records,
                     "typecast": typecast,
-                    "returnFieldsByFieldId": return_fields_by_field_id,
+                    "returnFieldsByFieldId": use_field_ids,
                     "performUpsert": {"fieldsToMergeOn": key_fields},
                 },
             )
diff --git a/pyairtable/orm/model.py b/pyairtable/orm/model.py
index 23c9521..1c36e03 100644
--- a/pyairtable/orm/model.py
+++ b/pyairtable/orm/model.py
@@ -191,7 +191,7 @@ class Model:
             "user_locale": None,
             "cell_format": "json",
             "time_zone": None,
-            "return_fields_by_field_id": cls._get_meta("use_field_ids", default=False),
+            "use_field_ids": cls._get_meta("use_field_ids", default=False),
         }
 
     @classmethod


Test patch:
diff --git a/tests/integration/test_integration_api.py b/tests/integration/test_integration_api.py
index 084c8b7..fecb7d9 100644
--- a/tests/integration/test_integration_api.py
+++ b/tests/integration/test_integration_api.py
@@ -50,38 +50,38 @@ def test_integration_table(table, cols):
     assert len(records) == COUNT
 
 
-def test_return_fields_by_field_id(table: Table, cols):
+def test_use_field_ids(table: Table, cols):
     """
     Test that we can get, create, and update records by field ID vs. name.
 
     See https://github.com/gtalarico/pyairtable/issues/194
     """
-    # Create one record with return_fields_by_field_id=True
-    record = table.create({cols.TEXT_ID: "Hello"}, return_fields_by_field_id=True)
+    # Create one record with use_field_ids=True
+    record = table.create({cols.TEXT_ID: "Hello"}, use_field_ids=True)
     assert record["fields"][cols.TEXT_ID] == "Hello"
 
-    # Update one record with return_fields_by_field_id=True
+    # Update one record with use_field_ids=True
     updated = table.update(
         record["id"],
         {cols.TEXT_ID: "Goodbye"},
-        return_fields_by_field_id=True,
+        use_field_ids=True,
     )
     assert updated["fields"][cols.TEXT_ID] == "Goodbye"
 
-    # Create multiple records with return_fields_by_field_id=True
+    # Create multiple records with use_field_ids=True
     records = table.batch_create(
         [
             {cols.TEXT_ID: "Alpha"},
             {cols.TEXT_ID: "Bravo"},
             {cols.TEXT_ID: "Charlie"},
         ],
-        return_fields_by_field_id=True,
+        use_field_ids=True,
     )
     assert records[0]["fields"][cols.TEXT_ID] == "Alpha"
     assert records[1]["fields"][cols.TEXT_ID] == "Bravo"
     assert records[2]["fields"][cols.TEXT_ID] == "Charlie"
 
-    # Update multiple records with return_fields_by_field_id=True
+    # Update multiple records with use_field_ids=True
     updates = [
         dict(
             record,
@@ -89,7 +89,7 @@ def test_return_fields_by_field_id(table: Table, cols):
         )
         for record in records
     ]
-    updated = table.batch_update(updates, return_fields_by_field_id=True)
+    updated = table.batch_update(updates, use_field_ids=True)
     assert updated[0]["fields"][cols.TEXT_ID] == "Hello, Alpha"
     assert updated[1]["fields"][cols.TEXT_ID] == "Hello, Bravo"
     assert updated[2]["fields"][cols.TEXT_ID] == "Hello, Charlie"
@@ -112,7 +112,7 @@ def test_get_records_options(table: Table, cols):
     assert table.all(sort=[cols.TEXT, cols.NUM]) == [rec]
     assert table.all(time_zone="utc") == [rec]
     assert table.all(user_locale="en-ie") == [rec]
-    assert table.all(return_fields_by_field_id=True) == [
+    assert table.all(use_field_ids=True) == [
         {
             "id": rec["id"],
             "createdTime": rec["createdTime"],
@@ -132,7 +132,7 @@ def test_get_records_options(table: Table, cols):
     assert table.all(formula=formula, sort=[cols.TEXT, cols.NUM]) == [rec]
     assert table.all(formula=formula, time_zone="utc") == [rec]
     assert table.all(formula=formula, user_locale="en-ie") == [rec]
-    assert table.all(formula=formula, return_fields_by_field_id=True) == [
+    assert table.all(formula=formula, use_field_ids=True) == [
         {
             "id": rec["id"],
             "createdTime": rec["createdTime"],
@@ -207,11 +207,11 @@ def test_batch_upsert(table: Table, cols):
     assert result["records"][2]["fields"] == {cols.TEXT: "Three", cols.NUM: 6}
     assert result["records"][3]["fields"] == {cols.NUM: 7}
 
-    # Test that batch_upsert passes along return_fields_by_field_id
+    # Test that batch_upsert passes along use_field_ids
     result = table.batch_upsert(
         [{"fields": {cols.TEXT: "Two", cols.NUM: 8}}],
         key_fields=[cols.TEXT],
-        return_fields_by_field_id=True,
+        use_field_ids=True,
     )
     assert result["records"] == [
         {
diff --git a/tests/test_orm_model.py b/tests/test_orm_model.py
index 4117c43..81e84e2 100644
--- a/tests/test_orm_model.py
+++ b/tests/test_orm_model.py
@@ -225,7 +225,7 @@ def test_passthrough(methodname, returns):
         a=1,
         b=2,
         c=3,
-        return_fields_by_field_id=getattr(FakeModel.Meta, "use_field_ids", False),
+        use_field_ids=getattr(FakeModel.Meta, "use_field_ids", False),
         user_locale=None,
         time_zone=None,
         cell_format="json",
diff --git a/tests/test_params.py b/tests/test_params.py
index bdf4e91..89b00aa 100644
--- a/tests/test_params.py
+++ b/tests/test_params.py
@@ -17,7 +17,7 @@ def test_params_integration(table, mock_records, mock_response_iterator):
         "view": "View",
         "sort": ["Name"],
         "fields": ["Name", "Age"],
-        "return_fields_by_field_id": True,
+        "use_field_ids": True,
     }
     with Mocker() as m:
         url_params = (
@@ -100,9 +100,9 @@ def test_params_integration(table, mock_records, mock_response_iterator):
             "?timeZone=America%2FChicago",
             # '?timeZone=America/Chicago'
         ],
-        ["return_fields_by_field_id", True, "?returnFieldsByFieldId=1"],
-        ["return_fields_by_field_id", 1, "?returnFieldsByFieldId=1"],
-        ["return_fields_by_field_id", False, "?returnFieldsByFieldId=0"],
+        ["use_field_ids", True, "?returnFieldsByFieldId=1"],
+        ["use_field_ids", 1, "?returnFieldsByFieldId=1"],
+        ["use_field_ids", False, "?returnFieldsByFieldId=0"],
         # TODO
         # [
         #     {"sort": [("Name", "desc"), ("Phone", "asc")]},
@@ -163,9 +163,9 @@ def test_convert_options_to_params(option, value, url_params):
             },
         ],
         ["cell_format", "string", {"cellFormat": "string"}],
-        ["return_fields_by_field_id", True, {"returnFieldsByFieldId": True}],
-        ["return_fields_by_field_id", 1, {"returnFieldsByFieldId": True}],
-        ["return_fields_by_field_id", False, {"returnFieldsByFieldId": False}],
+        ["use_field_ids", True, {"returnFieldsByFieldId": True}],
+        ["use_field_ids", 1, {"returnFieldsByFieldId": True}],
+        ["use_field_ids", False, {"returnFieldsByFieldId": False}],
         # userLocale and timeZone are not supported via POST, so they return "spare params"
         ["user_locale", "en-US", ({}, {"userLocale": "en-US"})],
         ["time_zone", "America/Chicago", ({}, {"timeZone": "America/Chicago"})],