Skip to content

Commit

Permalink
Documentation for Required*Field
Browse files Browse the repository at this point in the history
  • Loading branch information
mesozoic committed Apr 5, 2024
1 parent 9948c80 commit e969df6
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 28 deletions.
14 changes: 8 additions & 6 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,26 @@ Changelog
3.0 (TBD)
------------------------

* Rewrite of :mod:`pyairtable.formulas` module.
* Rewrite of :mod:`pyairtable.formulas` module. See :ref:`Building Formulas`.
- `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.
* :class:`~pyairtable.orm.fields.TextField` and
:class:`~pyairtable.orm.fields.CheckboxField` return ``""``
or ``False`` instead of ``None``.
- `PR #347 <https://github.com/gtalarico/pyairtable/pull/347>`_.
* 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.
for record links that should only contain one 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>`_.
* Renamed ``return_fields_by_field_id=`` to ``use_field_ids=``.
- `PR #362 <https://github.com/gtalarico/pyairtable/pull/362>`_.
* Added ORM fields that :ref:`require a non-null value <Required Values>`.

2.3.2 (2024-03-18)
------------------------
Expand Down
127 changes: 107 additions & 20 deletions docs/source/orm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,31 @@ read `Field types and cell values <https://airtable.com/developers/web/api/field
from operator import attrgetter
from pyairtable.orm import fields
cog.outl("..")
cog.outl(".. list-table::")
cog.outl(" :header-rows: 1\n")
cog.outl(" * - ORM field class")
cog.outl(" - Airtable field type(s)")
for cls in sorted(fields.ALL_FIELDS, key=attrgetter("__name__")):
links = re.findall(r"`.+? <.*?field-model.*?>`", cls.__doc__ or "")
ro = ' 🔒' if cls.readonly else ''
cog.outl(f" * - :class:`~pyairtable.orm.fields.{cls.__name__}`{ro}")
cog.outl(f" - {', '.join(f'{link}__' for link in links) if links else '(see docs)'}")
def cog_class_table(classes):
cog.outl(".. list-table::")
cog.outl(" :header-rows: 1\n")
cog.outl(" * - ORM field class")
cog.outl(" - Airtable field type(s)")
for cls in classes:
links = re.findall(r"`.+? <.*?field-model.*?>`", cls.__doc__ or "")
ro = ' 🔒' if cls.readonly else ''
cog.outl(f" * - :class:`~pyairtable.orm.fields.{cls.__name__}`{ro}")
cog.outl(f" - {', '.join(f'{link}__' for link in links) if links else '(see docs)'}")
classes = sorted(fields.ALL_FIELDS, key=attrgetter("__name__"))
optional = [cls for cls in classes if not cls.__name__.startswith("Required")]
required = [cls for cls in classes if cls.__name__.startswith("Required")]
cog.outl("..") # terminate the comment block
cog_class_table(optional)
cog.outl("")
cog.outl("Airtable does not have a concept of fields that require values,")
cog.outl("but pyAirtable allows you to enforce that concept within code")
cog.outl("using one of the following field classes.")
cog.outl("")
cog.outl("See :ref:`Required Values` for more details.")
cog.outl("")
cog_class_table(required)
]]]
..
.. list-table::
Expand Down Expand Up @@ -183,6 +197,28 @@ read `Field types and cell values <https://airtable.com/developers/web/api/field
- `Phone <https://airtable.com/developers/web/api/field-model#phone>`__
* - :class:`~pyairtable.orm.fields.RatingField`
- `Rating <https://airtable.com/developers/web/api/field-model#rating>`__
* - :class:`~pyairtable.orm.fields.RichTextField`
- `Rich text <https://airtable.com/developers/web/api/field-model#rich-text>`__
* - :class:`~pyairtable.orm.fields.SelectField`
- `Single select <https://airtable.com/developers/web/api/field-model#select>`__
* - :class:`~pyairtable.orm.fields.SingleLinkField`
- `Link to another record <https://airtable.com/developers/web/api/field-model#foreignkey>`__
* - :class:`~pyairtable.orm.fields.TextField`
- `Single line text <https://airtable.com/developers/web/api/field-model#simpletext>`__, `Long text <https://airtable.com/developers/web/api/field-model#multilinetext>`__
* - :class:`~pyairtable.orm.fields.UrlField`
- `Url <https://airtable.com/developers/web/api/field-model#urltext>`__

Airtable does not have a concept of fields that require values,
but pyAirtable allows you to enforce that concept within code
using one of the following field classes.

See :ref:`Required Values` for more details.

.. list-table::
:header-rows: 1

* - ORM field class
- Airtable field type(s)
* - :class:`~pyairtable.orm.fields.RequiredAITextField` 🔒
- `AI Text <https://airtable.com/developers/web/api/field-model#aitext>`__
* - :class:`~pyairtable.orm.fields.RequiredBarcodeField`
Expand All @@ -199,6 +235,8 @@ read `Field types and cell values <https://airtable.com/developers/web/api/field
- `Date and time <https://airtable.com/developers/web/api/field-model#dateandtime>`__
* - :class:`~pyairtable.orm.fields.RequiredDurationField`
- `Duration <https://airtable.com/developers/web/api/field-model#durationnumber>`__
* - :class:`~pyairtable.orm.fields.RequiredEmailField`
- `Email <https://airtable.com/developers/web/api/field-model#email>`__
* - :class:`~pyairtable.orm.fields.RequiredFloatField`
- `Number <https://airtable.com/developers/web/api/field-model#decimalorintegernumber>`__
* - :class:`~pyairtable.orm.fields.RequiredIntegerField`
Expand All @@ -207,21 +245,19 @@ read `Field types and cell values <https://airtable.com/developers/web/api/field
- `Number <https://airtable.com/developers/web/api/field-model#decimalorintegernumber>`__
* - :class:`~pyairtable.orm.fields.RequiredPercentField`
- `Percent <https://airtable.com/developers/web/api/field-model#percentnumber>`__
* - :class:`~pyairtable.orm.fields.RequiredPhoneNumberField`
- `Phone <https://airtable.com/developers/web/api/field-model#phone>`__
* - :class:`~pyairtable.orm.fields.RequiredRatingField`
- `Rating <https://airtable.com/developers/web/api/field-model#rating>`__
* - :class:`~pyairtable.orm.fields.RequiredSelectField`
- `Single select <https://airtable.com/developers/web/api/field-model#select>`__
* - :class:`~pyairtable.orm.fields.RichTextField`
* - :class:`~pyairtable.orm.fields.RequiredRichTextField`
- `Rich text <https://airtable.com/developers/web/api/field-model#rich-text>`__
* - :class:`~pyairtable.orm.fields.SelectField`
* - :class:`~pyairtable.orm.fields.RequiredSelectField`
- `Single select <https://airtable.com/developers/web/api/field-model#select>`__
* - :class:`~pyairtable.orm.fields.SingleLinkField`
- `Link to another record <https://airtable.com/developers/web/api/field-model#foreignkey>`__
* - :class:`~pyairtable.orm.fields.TextField`
* - :class:`~pyairtable.orm.fields.RequiredTextField`
- `Single line text <https://airtable.com/developers/web/api/field-model#simpletext>`__, `Long text <https://airtable.com/developers/web/api/field-model#multilinetext>`__
* - :class:`~pyairtable.orm.fields.UrlField`
* - :class:`~pyairtable.orm.fields.RequiredUrlField`
- `Url <https://airtable.com/developers/web/api/field-model#urltext>`__
.. [[[end]]]
.. [[[end]]] (checksum: 131138e1071ba71d4f46f05da4d57570)
Formula, Rollup, and Lookup Fields
Expand Down Expand Up @@ -275,6 +311,57 @@ You can check for errors using the :func:`~pyairtable.api.types.is_airtable_erro
True


Required Values
---------------

Airtable does not generally have a concept of fields that require values, but
pyAirtable allows you to enforce that a field must have a value before saving it.
To do this, use one of the "Required" field types, which will raise an exception
if either of the following occur:

1. If you try to set its value to ``None`` (or, sometimes, to the empty string).
2. If the API returns a ``None`` (or empty string) as the field's value.

For example, given this code:

.. code-block:: python
from pyairtable.orm import Model, fields as F
class MyTable(Model):
class Meta:
...
name = F.RequiredTextField("Name")
The following will all raise an exception:

.. code-block:: python
>>> MyTable(name=None)
Traceback (most recent call last):
...
MissingValue: MyTable.name does not accept empty values
>>> r = MyTable.from_record(fake_record(Name="Alice"))
>>> r.name
'Alice'
>>> r.name = None
Traceback (most recent call last):
...
MissingValue: MyTable.name does not accept empty values
>>> r = MyTable.from_record(fake_record(Name=None))
>>> r.name
Traceback (most recent call last):
...
MissingValue: MyTable.name received an empty value
One reason to use these fields (sparingly!) might be to avoid adding defensive
null-handling checks all over your code, if you are confident that the workflows
around your Airtable base will not produce an empty value (or that an empty value
is enough of a problem that your code should raise an exception).

Linked Records
----------------

Expand Down
2 changes: 0 additions & 2 deletions pyairtable/orm/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -1366,8 +1366,6 @@ class CreatedTimeField(RequiredDatetimeField):
See `Created time <https://airtable.com/developers/web/api/field-model#createdtime>`__.
If the Airtable API returns ``null``, this field will raise :class:`~pyairtable.orm.fields.MissingValue`.
If the Airtable API returns ``null``, this field will raise :class:`~pyairtable.orm.fields.MissingValue`.
"""

Expand Down

0 comments on commit e969df6

Please sign in to comment.