Skip to content

Commit

Permalink
Introduce "Collaborator" model
Browse files Browse the repository at this point in the history
  • Loading branch information
mesozoic committed Jul 31, 2023
1 parent a3b471a commit f9bfe96
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 20 deletions.
2 changes: 2 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Module: pyairtable.models

.. autoclass:: pyairtable.models.Comment
:members:
.. autoclass:: pyairtable.models.Collaborator
:members:

Module: pyairtable.orm
*******************************
Expand Down
8 changes: 4 additions & 4 deletions docs/source/orm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,10 @@ comments on a particular record, just like their :class:`~pyairtable.Table` equi
type='user'
)
},
author={
'id': 'usr0000pyairtable',
'email': 'pyairtable@example.com',
'name': 'Your pyairtable access token'
author=Collaborator(
id='usr0000pyairtable',
email='pyairtable@example.com',
name='Your pyairtable access token'
}
)
]
Expand Down
8 changes: 4 additions & 4 deletions docs/source/tables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,10 @@ and :meth:`~pyairtable.Table.add_comment` methods will return instances of
type='user'
)
},
author={
'id': 'usr0000pyairtable',
'email': 'pyairtable@example.com',
'name': 'Your pyairtable access token'
author=Collaborator(
id='usr0000pyairtable',
email='pyairtable@example.com',
name='Your pyairtable access token'
}
)
]
Expand Down
8 changes: 4 additions & 4 deletions pyairtable/api/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,10 +489,10 @@ def comments(self, record_id: RecordId) -> List["pyairtable.models.Comment"]:
type='user'
)
},
author={
'id': 'usr0000pyairtable',
'email': 'pyairtable@example.com',
'name': 'Your pyairtable access token'
author=Collaborator(
id='usr0000pyairtable',
email='pyairtable@example.com',
name='Your pyairtable access token'
}
)
]
Expand Down
2 changes: 2 additions & 0 deletions pyairtable/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from .collaborator import Collaborator
from .comment import Comment

__all__ = [
"Collaborator",
"Comment",
]
37 changes: 37 additions & 0 deletions pyairtable/models/collaborator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import Any, Dict, Optional

from pydantic import root_validator
from typing_extensions import TypeAlias

from ._base import AirtableModel

UserId: TypeAlias = str


class Collaborator(AirtableModel):
"""
Represents an Airtable user being passed from the API.
This is only used in contexts involving other models (e.g. :class:`~pyairtable.models.Comment`).
In contexts where API values are returned as ``dict``, we will return
collaborator information as a ``dict`` as well.
"""

#: Airtable's unique ID for the user, in the format ``usrXXXXXXXXXXXXXX``.
id: Optional[UserId]

#: The email address of the user.
email: Optional[str]

#: The display name of the user.
name: Optional[str]

@root_validator
@classmethod
def check_has_id_or_email(cls, values: Dict[Any, Any]) -> Dict[Any, Any]:
"""
:meta private:
"""
if not (values.get("id") or values.get("email")):
raise ValueError("id or email is required")
return values
14 changes: 7 additions & 7 deletions pyairtable/models/comment.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Dict, Optional

from pyairtable.api.types import CollaboratorDict
from pyairtable.models._base import AirtableModel, SerializableModel
from ._base import AirtableModel, SerializableModel
from .collaborator import Collaborator


class Comment(SerializableModel):
Expand All @@ -24,10 +24,10 @@ class Comment(SerializableModel):
type='user'
)
},
author={
'id': 'usrL2xZC5xoH4luAi',
'email': 'pyairtable@example.com',
'name': 'Your pyairtable access token'
author=Collaborator(
id='usr0000pyairtable',
email='pyairtable@example.com',
name='Your pyairtable access token'
}
)
]
Expand All @@ -51,7 +51,7 @@ class Comment(SerializableModel):
last_updated_time: Optional[str]

#: The account which created the comment.
author: CollaboratorDict
author: Collaborator

#: Users or groups that were mentioned in the text.
mentioned: Optional[Dict[str, "Comment.Mentioned"]]
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_integration_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ def test_integration_comments(api, table: Table, cols):
comments = table.comments(record["id"])
assert len(comments) == 1
assert whoami in comments[0].text
assert comments[0].author
assert comments[0].author.id == whoami
assert comments[0].mentioned[whoami].id == whoami

# Test that we can modify the comment and examine its updated state
Expand Down
34 changes: 34 additions & 0 deletions tests/test_models_collaborator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import pytest

from pyairtable.models import Collaborator

fake_user_data = {
"id": "usr000000fakeuser",
"email": "fake@example.com",
"name": "Fake User",
}


def test_parse():
user = Collaborator.parse_obj(fake_user_data)
assert user.id == fake_user_data["id"]
assert user.email == fake_user_data["email"]
assert user.name == fake_user_data["name"]


def test_init():
c = Collaborator(id="usrXXXXXXXXXXXXX", name="Fake User")
assert c.id == "usrXXXXXXXXXXXXX"
assert c.email is None
assert c.name == "Fake User"

c = Collaborator(email="fake@example.com")
assert c.id is None
assert c.email == "fake@example.com"
assert c.name is None

with pytest.raises(ValueError):
Collaborator()

with pytest.raises(ValueError):
Collaborator(name="Fake User")
11 changes: 11 additions & 0 deletions tests/test_models_comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ def test_parse(comment_json):
Comment.parse_obj(comment_json)


@pytest.mark.parametrize("attr", ["mentioned", "last_updated_time"])
def test_missing_attributes(comment_json, attr):
"""
Test that we can parse the payload when missing optional values.
"""
del comment_json[Comment.__fields__[attr].alias]
comment = Comment.parse_obj(comment_json)
assert getattr(comment, attr) is None


@pytest.mark.parametrize(
"attr,value",
[
Expand Down Expand Up @@ -82,6 +92,7 @@ def test_save(comment, requests_mock):

# ...but our model loaded whatever values the API sent back.
assert comment.text == new_text
assert comment.author.email == "author@example.com"
assert not comment.mentioned


Expand Down

0 comments on commit f9bfe96

Please sign in to comment.